From 1d94f6d303e98a43e11e8b05640d6496cf67603b Mon Sep 17 00:00:00 2001 From: Taus Brock-Nannestad Date: Tue, 17 Dec 2019 17:19:49 +0100 Subject: [PATCH 001/148] Python: Fix several bad join orders. Performance on `taers232c/GAMADV-X` (which exhibited pathological behaviour in the most recent dist upgrade) went from ~670s to ~313s on `py/hardcoded-credentials`. There are still a few tuple counts in the 10-100 million range, but this commit takes care of all of the ones that numbered in the billions. (A single tuple count in the 100-1000 million range remains, but it appears to be less critical, taking only two seconds to calculate.) --- python/ql/src/semmle/python/essa/Essa.qll | 19 +++++++++++--- .../src/semmle/python/pointsto/PointsTo.qll | 25 ++++++++++++++++--- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/python/ql/src/semmle/python/essa/Essa.qll b/python/ql/src/semmle/python/essa/Essa.qll index cbc8df53a63..b7903d05981 100644 --- a/python/ql/src/semmle/python/essa/Essa.qll +++ b/python/ql/src/semmle/python/essa/Essa.qll @@ -50,8 +50,16 @@ class EssaVariable extends TEssaDefinition { * Note that this differs from `EssaVariable.getAUse()`. */ ControlFlowNode getASourceUse() { + exists(SsaSourceVariable var | + result = use_for_var(var) and + result = var.getASourceUse() + ) + } + + pragma[nomagic] + private ControlFlowNode use_for_var(SsaSourceVariable var) { result = this.getAUse() and - result = this.getSourceVariable().getASourceUse() + var = this.getSourceVariable() } /** Gets the scope of this variable. */ @@ -268,11 +276,16 @@ class PhiFunction extends EssaDefinition, TPhiFunction { not exists(this.inputEdgeRefinement(result)) } + pragma[noinline] + private SsaSourceVariable pred_var(BasicBlock pred) { + result = this.getSourceVariable() and + pred = this.nonPiInput() + } + /** Gets another definition of the same source variable that reaches this definition. */ private EssaDefinition reachingDefinition(BasicBlock pred) { result.getScope() = this.getScope() and - result.getSourceVariable() = this.getSourceVariable() and - pred = this.nonPiInput() and + result.getSourceVariable() = pred_var(pred) and result.reachesEndOfBlock(pred) } diff --git a/python/ql/src/semmle/python/pointsto/PointsTo.qll b/python/ql/src/semmle/python/pointsto/PointsTo.qll index 22f1dea2171..72e45b38016 100644 --- a/python/ql/src/semmle/python/pointsto/PointsTo.qll +++ b/python/ql/src/semmle/python/pointsto/PointsTo.qll @@ -578,6 +578,13 @@ cached module PointsToInternal { ) or /* Undefined variable */ + undefined_variable(def, context, value, origin) + or + /* Builtin not defined in outer scope */ + builtin_not_in_outer_scope(def, context, value, origin) + } + + private predicate undefined_variable(ScopeEntryDefinition def, PointsToContext context, ObjectInternal value, ControlFlowNode origin) { exists(Scope scope | not def.getVariable().getName() = "__name__" and not def.getVariable().isMetaVariable() and @@ -587,8 +594,9 @@ cached module PointsToInternal { def.getSourceVariable() instanceof LocalVariable and (context.isImport() or context.isRuntime() or context.isMain()) ) and value = ObjectInternal::undefined() and origin = def.getDefiningNode() - or - /* Builtin not defined in outer scope */ + } + + private predicate builtin_not_in_outer_scope(ScopeEntryDefinition def, PointsToContext context, ObjectInternal value, ControlFlowNode origin) { exists(Module mod, GlobalVariable var | var = def.getSourceVariable() and mod = def.getScope().getEnclosingModule() and @@ -1113,8 +1121,17 @@ module InterProceduralPointsTo { * Transfer of values from the callsite to the callee, for enclosing variables, but not arguments/parameters. */ pragma [noinline] private predicate callsite_entry_value_transfer(EssaVariable caller_var, PointsToContext caller, ScopeEntryDefinition entry_def, PointsToContext callee) { - entry_def.getSourceVariable() = caller_var.getSourceVariable() and - callsite_calls_function(caller_var.getAUse(), caller, entry_def.getScope(), callee, _) + exists(ControlFlowNode use, SsaSourceVariable var | + var_and_use(caller_var, use, var) and + entry_def.getSourceVariable() = var and + callsite_calls_function(use, caller, entry_def.getScope(), callee, _) + ) + } + + pragma[nomagic] + private predicate var_and_use(EssaVariable caller_var, ControlFlowNode use, SsaSourceVariable var) { + use = caller_var.getAUse() and + var = caller_var.getSourceVariable() } /** Helper for `scope_entry_value_transfer`. */ From 851d69299607fa279709bb0d59019513993fe2bb Mon Sep 17 00:00:00 2001 From: Taus Brock-Nannestad Date: Mon, 6 Jan 2020 13:40:52 +0100 Subject: [PATCH 002/148] Python: Remove manual TC from `ssaShortCut`. This caused a massive slowdown on certain snapshots. --- python/ql/src/semmle/python/pointsto/PointsTo.qll | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/python/ql/src/semmle/python/pointsto/PointsTo.qll b/python/ql/src/semmle/python/pointsto/PointsTo.qll index 22f1dea2171..a43fa5d1156 100644 --- a/python/ql/src/semmle/python/pointsto/PointsTo.qll +++ b/python/ql/src/semmle/python/pointsto/PointsTo.qll @@ -284,7 +284,7 @@ cached module PointsToInternal { ssa_definition_points_to(var.getDefinition(), context, value, origin) or exists(EssaVariable prev | - ssaShortCut(prev, var) and + ssaShortCut+(prev, var) and variablePointsTo(prev, context, value, origin) ) } @@ -305,10 +305,6 @@ cached module PointsToInternal { start = def.getInput() and end.getDefinition() = def ) - or - exists(EssaVariable mid | - ssaShortCut(start, mid) and ssaShortCut(mid, end) - ) } pragma [noinline] From ec5896abbab4effa8c82283badea921c9fa086e8 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 19 Dec 2019 09:54:00 +0100 Subject: [PATCH 003/148] add additional data-flow edges to data-flow related to promises --- .../ql/src/semmle/javascript/Promises.qll | 373 +++++++++++++++++- .../src/semmle/javascript/StandardLibrary.qll | 197 +-------- .../InterProceduralFlow/DataFlow.expected | 4 +- .../TaintTracking.expected | 2 - .../InterProceduralFlow/promises.js | 2 +- .../test/library-tests/Promises/Flowsteps.qll | 5 + .../test/library-tests/Promises/flowsteps.js | 56 +++ .../library-tests/Promises/tests.expected | 93 +++++ .../ql/test/library-tests/Promises/tests.ql | 1 + 9 files changed, 533 insertions(+), 200 deletions(-) create mode 100644 javascript/ql/test/library-tests/Promises/Flowsteps.qll create mode 100644 javascript/ql/test/library-tests/Promises/flowsteps.js diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index d74aafc265c..ee4b42addcc 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -1,9 +1,380 @@ /** - * Provides classes for modelling promise libraries. + * Provides classes for modelling promises and their data-flow. */ import javascript +/** + * A definition of a `Promise` object. + */ +abstract class PromiseDefinition extends DataFlow::SourceNode { + /** Gets the executor function of this promise object. */ + abstract DataFlow::FunctionNode getExecutor(); + + /** Gets the `resolve` parameter of the executor function. */ + DataFlow::ParameterNode getResolveParameter() { result = getExecutor().getParameter(0) } + + /** Gets the `reject` parameter of the executor function. */ + DataFlow::ParameterNode getRejectParameter() { result = getExecutor().getParameter(1) } + + /** Gets the `i`th callback handler installed by method `m`. */ + private DataFlow::FunctionNode getAHandler(string m, int i) { + result = getAMethodCall(m).getCallback(i) + } + + /** + * Gets a function that handles promise resolution, including both + * `then` handlers and `finally` handlers. + */ + DataFlow::FunctionNode getAResolveHandler() { + result = getAHandler("then", 0) or + result = getAFinallyHandler() + } + + /** + * Gets a function that handles promise rejection, including + * `then` handlers, `catch` handlers and `finally` handlers. + */ + DataFlow::FunctionNode getARejectHandler() { + result = getAHandler("then", 1) or + result = getACatchHandler() or + result = getAFinallyHandler() + } + + /** + * Gets a `catch` handler of this promise. + */ + DataFlow::FunctionNode getACatchHandler() { result = getAHandler("catch", 0) } + + /** + * Gets a `finally` handler of this promise. + */ + DataFlow::FunctionNode getAFinallyHandler() { result = getAHandler("finally", 0) } +} + +/** Holds if the `i`th callback handler is installed by method `m`. */ +private predicate hasHandler(DataFlow::InvokeNode promise, string m, int i) { + exists(promise.getAMethodCall(m).getCallback(i)) +} + +/** + * A call that looks like a Promise. + * + * For example, this could be the call `promise(f).then(function(v){...})` + */ +class PromiseCandidate extends DataFlow::InvokeNode { + PromiseCandidate() { + hasHandler(this, "then", [0 .. 1]) or + hasHandler(this, "catch", 0) or + hasHandler(this, "finally", 0) + } +} + +/** + * A promise object created by the standard ECMAScript 2015 `Promise` constructor. + */ +private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNode { + ES2015PromiseDefinition() { this = DataFlow::globalVarRef("Promise").getAnInstantiation() } + + override DataFlow::FunctionNode getExecutor() { result = getCallback(0) } +} + +/** + * A promise that is created and resolved with one or more value. + */ +abstract class PromiseCreationCall extends DataFlow::CallNode { + /** + * Gets the value this promise is resolved with. + */ + abstract DataFlow::Node getValue(); +} + +/** + * A promise that is created using a `.resolve()` call. + */ +abstract class ResolvedPromiseDefinition extends PromiseCreationCall { } + +/** + * A resolved promise created by the standard ECMAScript 2015 `Promise.resolve` function. + */ +class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition { + ResolvedES2015PromiseDefinition() { + this = DataFlow::globalVarRef("Promise").getAMemberCall("resolve") + } + + override DataFlow::Node getValue() { result = getArgument(0) } +} + +/** + * An aggregated promise produced either by `Promise.all` or `Promise.race`. + */ +class AggregateES2015PromiseDefinition extends PromiseCreationCall { + AggregateES2015PromiseDefinition() { + exists(string m | m = "all" or m = "race" | + this = DataFlow::globalVarRef("Promise").getAMemberCall(m) + ) + } + + override DataFlow::Node getValue() { + result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement() + } +} + +/** + * This module defines how data-flow propagates into and out a Promise. + */ +private module PromiseFlow { + /** + * A promise from which data-flow can flow into or out of. + * + * This promise can both be a promise created by e.g. `new Promise(..)` or `Promise.resolve(..)`, + * or the result from calling a method on a promise e.g. `promise.then(..)`. + * + * The 4 methods in this class describe that ordinary and exceptional flow can flow into and out of this promise. + */ + private abstract class PromiseNode extends DataFlow::SourceNode { + + /** + * Get a DataFlow::Node for a value that this promise is resolved with. + * The value is sent either to a chained promise, or to an `await` expression. + * + * The value is e.g. an argument to `resolve(..)`, or a return value from a `.then(..)` handler. + */ + DataFlow::Node getASentResolveValue() { none() } + + /** + * Get the DataFlow::Node that receives the value that this promise has been resolved with. + * + * E.g. the `x` in `promise.then((x) => ..)`. + */ + DataFlow::Node getReceivedResolveValue() { none() } + + /** + * Get a DataFlow::Node for a value that this promise is rejected with. + * The value is sent either to a chained promise, or thrown by an `await` expression. + * + * The value is e.g. an argument to `reject(..)`, or an exception thrown by the promise executor. + */ + DataFlow::Node getASentRejectValue() { none() } + + /** + * Get the DataFlow::Node that receives the value that this promise has been rejected with. + * + * E.g. the `x` in `promise.catch((x) => ..)`. + */ + DataFlow::Node getReceivedRejectValue() { none() } + } + + /** + * A PromiseNode for a PromiseDefinition. + * E.g. `new Promise(..)`. + */ + private class PromiseDefinitionNode extends PromiseNode { + PromiseDefinition promise; + + PromiseDefinitionNode() { this = promise } + + override DataFlow::Node getASentResolveValue() { + result = promise.getResolveParameter().getACall().getArgument(0) + } + + override DataFlow::Node getASentRejectValue() { + result = promise.getRejectParameter().getACall().getArgument(0) + or + result = promise.getExecutor().getExceptionalReturn() + } + } + + /** + * A PromiseNode for a call that creates a promise. + * E.g. `Promise.resolve(..)` or `Promise.all(..)`. + */ + private class PromiseCreationNode extends PromiseNode { + PromiseCreationCall promise; + + PromiseCreationNode() { this = promise } + + override DataFlow::Node getASentResolveValue() { + exists(DataFlow::Node value | value = promise.getValue() | + not value instanceof PromiseNode and + result = value + or + result = value.(PromiseNode).getASentResolveValue() + ) + } + + override DataFlow::Node getASentRejectValue() { + result = promise.getValue().(PromiseNode).getASentRejectValue() + } + } + + /** + * A node referring to a PromiseNode through type-tracking. + */ + private class TrackedPromiseNode extends PromiseNode { + PromiseNode base; + TrackedPromiseNode() { + this = trackPromise(DataFlow::TypeTracker::end(), base) and + not this instanceof PromiseDefinitionNode and + not this instanceof PromiseCreationNode + } + + override DataFlow::Node getASentResolveValue() { result = base.getASentResolveValue() } + override DataFlow::Node getReceivedResolveValue() { result = base.getReceivedResolveValue() } + override DataFlow::Node getASentRejectValue() { result = base.getASentRejectValue() } + override DataFlow::Node getReceivedRejectValue() { result = base.getReceivedRejectValue() } + } + + private DataFlow::SourceNode trackPromise(DataFlow::TypeTracker t, PromiseNode promise) { + t.start() and result = promise + or + exists(DataFlow::TypeTracker t2 | result = trackPromise(t2, promise).track(t2, t)) + } + + /** + * A PromiseNode that is a method call on an existing PromiseNode. + * E.g. `promise.then(..)`. + */ + private abstract class ChainedPromiseNode extends PromiseNode, DataFlow::MethodCallNode { + PromiseNode base; + + ChainedPromiseNode() { this = base.getAMethodCall(_) } + + PromiseNode getBase() { result = base } + } + + /** + * A PromiseNode for the `.then(..)` method on an existing promise. + */ + private class PromiseThenNode extends ChainedPromiseNode { + PromiseThenNode() { this = base.getAMethodCall("then") } + + override DataFlow::Node getASentResolveValue() { + exists(DataFlow::Node ret | ret = this.getCallback(0).getAReturn() | + if ret instanceof PromiseNode + then result = ret.(PromiseNode).getReceivedResolveValue() + else result = ret + ) + } + + override DataFlow::Node getASentRejectValue() { + not exists(this.getCallback(1)) and result = base.getASentRejectValue() + or + result = this.getCallback([0..1]).getExceptionalReturn() + } + + override DataFlow::Node getReceivedResolveValue() { result = this.getCallback(0).getParameter(0) } + + override DataFlow::Node getReceivedRejectValue() { result = this.getCallback(1).getParameter(0) } + } + + /** + * A PromiseNode for the `.finally(..)` method on an existing promise. + */ + private class PromiseFinallyNode extends ChainedPromiseNode { + PromiseFinallyNode() { this = base.getAMethodCall("finally") } + + override DataFlow::Node getASentResolveValue() { result = base.getASentResolveValue() } + + override DataFlow::Node getASentRejectValue() { + result = base.getASentRejectValue() + or + result = this.getCallback(0).getExceptionalReturn() + } + } + + /** + * A PromiseNode for the `.catch(..)` method on an existing promise. + */ + private class PromiseCatchNode extends ChainedPromiseNode { + PromiseCatchNode() { this = base.getAMethodCall("catch") } + + override DataFlow::Node getASentResolveValue() { + exists(DataFlow::Node ret | ret = this.getCallback(0).getAReturn() | + if ret instanceof PromiseNode + then result = ret.(PromiseNode).getReceivedResolveValue() + else result = ret + ) + or + result = base.getASentResolveValue() + } + + override DataFlow::Node getASentRejectValue() { result = this.getCallback(0).getExceptionalReturn() } + + override DataFlow::Node getReceivedResolveValue() { none() } + + override DataFlow::Node getReceivedRejectValue() { result = this.getCallback(0).getParameter(0) } + } + + + private ChainedPromiseNode getAChainedPromise(PromiseNode p) { result.getBase() = p} + + /** + * A data flow edge from a promise resolve/reject to the corresponding handler (or `await` expression). + */ + private class PromiseFlowStep extends DataFlow::AdditionalFlowStep { + PromiseNode promise; + + PromiseFlowStep() { this = promise } + + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + pred = promise.getASentResolveValue() and + succ = getAChainedPromise(promise).getReceivedResolveValue() + or + pred = promise.getASentRejectValue() and + succ = getAChainedPromise(promise).getReceivedRejectValue() + or + pred = promise.getASentResolveValue() and + exists(DataFlow::SourceNode awaitNode | + awaitNode.asExpr().(AwaitExpr).getOperand() = promise.asExpr() and + succ = awaitNode + ) + or + pred = promise.getASentRejectValue() and + exists(DataFlow::SourceNode awaitNode | + awaitNode.asExpr().(AwaitExpr).getOperand() = promise.asExpr() and + succ = awaitNode.asExpr().getExceptionTarget() + ) + } + } +} + +/** + * Holds if taint propagates from `pred` to `succ` through promises. + */ +predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) { + // from `x` to `new Promise((res, rej) => res(x))` + pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0) + or + // from `x` to `Promise.resolve(x)` + pred = succ.(PromiseCreationCall).getValue() + or + exists(DataFlow::MethodCallNode thn, DataFlow::FunctionNode cb | + thn.getMethodName() = "then" and cb = thn.getCallback(0) + | + // from `p` to `x` in `p.then(x => ...)` + pred = thn.getReceiver() and + succ = cb.getParameter(0) + or + // from `v` to `p.then(x => return v)` + pred = cb.getAReturn() and + succ = thn + ) +} + +/** + * An additional taint step that involves promises. + */ +private class PromiseTaintStep extends TaintTracking::AdditionalTaintStep { + DataFlow::Node source; + + PromiseTaintStep() { promiseTaintStep(source, this) } + + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + pred = source and succ = this + } +} + /** * Provides classes for working with the `bluebird` library (http://bluebirdjs.com). */ diff --git a/javascript/ql/src/semmle/javascript/StandardLibrary.qll b/javascript/ql/src/semmle/javascript/StandardLibrary.qll index 03b15b012e0..0de4cce8e05 100644 --- a/javascript/ql/src/semmle/javascript/StandardLibrary.qll +++ b/javascript/ql/src/semmle/javascript/StandardLibrary.qll @@ -76,191 +76,6 @@ private class AnalyzedThisInArrayIterationFunction extends AnalyzedNode, DataFlo } } -/** - * A definition of a `Promise` object. - */ -abstract class PromiseDefinition extends DataFlow::SourceNode { - /** Gets the executor function of this promise object. */ - abstract DataFlow::FunctionNode getExecutor(); - - /** Gets the `resolve` parameter of the executor function. */ - DataFlow::ParameterNode getResolveParameter() { result = getExecutor().getParameter(0) } - - /** Gets the `reject` parameter of the executor function. */ - DataFlow::ParameterNode getRejectParameter() { result = getExecutor().getParameter(1) } - - /** Gets the `i`th callback handler installed by method `m`. */ - private DataFlow::FunctionNode getAHandler(string m, int i) { - result = getAMethodCall(m).getCallback(i) - } - - /** - * Gets a function that handles promise resolution, including both - * `then` handlers and `finally` handlers. - */ - DataFlow::FunctionNode getAResolveHandler() { - result = getAHandler("then", 0) or - result = getAFinallyHandler() - } - - /** - * Gets a function that handles promise rejection, including - * `then` handlers, `catch` handlers and `finally` handlers. - */ - DataFlow::FunctionNode getARejectHandler() { - result = getAHandler("then", 1) or - result = getACatchHandler() or - result = getAFinallyHandler() - } - - /** - * Gets a `catch` handler of this promise. - */ - DataFlow::FunctionNode getACatchHandler() { result = getAHandler("catch", 0) } - - /** - * Gets a `finally` handler of this promise. - */ - DataFlow::FunctionNode getAFinallyHandler() { result = getAHandler("finally", 0) } -} - -/** Holds if the `i`th callback handler is installed by method `m`. */ -private predicate hasHandler(DataFlow::InvokeNode promise, string m, int i) { - exists(promise.getAMethodCall(m).getCallback(i)) -} - -/** - * A call that looks like a Promise. - * - * For example, this could be the call `promise(f).then(function(v){...})` - */ -class PromiseCandidate extends DataFlow::InvokeNode { - PromiseCandidate() { - hasHandler(this, "then", [0 .. 1]) or - hasHandler(this, "catch", 0) or - hasHandler(this, "finally", 0) - } -} - -/** - * A promise object created by the standard ECMAScript 2015 `Promise` constructor. - */ -private class ES2015PromiseDefinition extends PromiseDefinition, DataFlow::NewNode { - ES2015PromiseDefinition() { this = DataFlow::globalVarRef("Promise").getAnInstantiation() } - - override DataFlow::FunctionNode getExecutor() { result = getCallback(0) } -} - -/** - * A promise that is created and resolved with one or more value. - */ -abstract class PromiseCreationCall extends DataFlow::CallNode { - /** - * Gets the value this promise is resolved with. - */ - abstract DataFlow::Node getValue(); -} - -/** - * A promise that is created using a `.resolve()` call. - */ -abstract class ResolvedPromiseDefinition extends PromiseCreationCall {} - -/** - * A resolved promise created by the standard ECMAScript 2015 `Promise.resolve` function. - */ -class ResolvedES2015PromiseDefinition extends ResolvedPromiseDefinition { - ResolvedES2015PromiseDefinition() { - this = DataFlow::globalVarRef("Promise").getAMemberCall("resolve") - } - - override DataFlow::Node getValue() { result = getArgument(0) } -} - -/** - * An aggregated promise produced either by `Promise.all` or `Promise.race`. - */ -class AggregateES2015PromiseDefinition extends PromiseCreationCall { - AggregateES2015PromiseDefinition() { - exists(string m | m = "all" or m = "race" | - this = DataFlow::globalVarRef("Promise").getAMemberCall(m) - ) - } - - override DataFlow::Node getValue() { - result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement() - } -} - -/** - * A data flow edge from a promise reaction to the corresponding handler. - */ -private class PromiseFlowStep extends DataFlow::AdditionalFlowStep { - PromiseDefinition p; - - PromiseFlowStep() { this = p } - - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - pred = p.getResolveParameter().getACall().getArgument(0) and - succ = p.getAResolveHandler().getParameter(0) - or - pred = p.getRejectParameter().getACall().getArgument(0) and - succ = p.getARejectHandler().getParameter(0) - } -} - -/** - * A data flow edge from the exceptional return of the promise executor to the promise catch handler. - * This only adds an edge from the exceptional return of the promise executor to a `.catch()` handler. - */ -private class PromiseExceptionalStep extends DataFlow::AdditionalFlowStep { - PromiseDefinition promise; - PromiseExceptionalStep() { - promise = this - } - - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - pred = promise.getExecutor().getExceptionalReturn() and - succ = promise.getACatchHandler().getParameter(0) - } -} - -/** - * Holds if taint propagates from `pred` to `succ` through promises. - */ -predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) { - // from `x` to `new Promise((res, rej) => res(x))` - pred = succ.(PromiseDefinition).getResolveParameter().getACall().getArgument(0) - or - // from `x` to `Promise.resolve(x)` - pred = succ.(PromiseCreationCall).getValue() - or - exists(DataFlow::MethodCallNode thn, DataFlow::FunctionNode cb | - thn.getMethodName() = "then" and cb = thn.getCallback(0) - | - // from `p` to `x` in `p.then(x => ...)` - pred = thn.getReceiver() and - succ = cb.getParameter(0) - or - // from `v` to `p.then(x => return v)` - pred = cb.getAReturn() and - succ = thn - ) -} - -/** - * An additional taint step that involves promises. - */ -private class PromiseTaintStep extends TaintTracking::AdditionalTaintStep { - DataFlow::Node source; - - PromiseTaintStep() { promiseTaintStep(source, this) } - - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - pred = source and succ = this - } -} - /** * A flow step propagating the exception thrown from a callback to a method whose name coincides * a built-in Array iteration method, such as `forEach` or `map`. @@ -298,9 +113,7 @@ class StringReplaceCall extends DataFlow::MethodCallNode { } /** Gets the regular expression passed as the first argument to `replace`, if any. */ - DataFlow::RegExpLiteralNode getRegExp() { - result.flowsTo(getArgument(0)) - } + DataFlow::RegExpLiteralNode getRegExp() { result.flowsTo(getArgument(0)) } /** Gets a string that is being replaced by this call. */ string getAReplacedString() { @@ -312,17 +125,13 @@ class StringReplaceCall extends DataFlow::MethodCallNode { * Gets the second argument of this call to `replace`, which is either a string * or a callback. */ - DataFlow::Node getRawReplacement() { - result = getArgument(1) - } + DataFlow::Node getRawReplacement() { result = getArgument(1) } /** * Holds if this is a global replacement, that is, the first argument is a regular expression * with the `g` flag. */ - predicate isGlobal() { - getRegExp().isGlobal() - } + predicate isGlobal() { getRegExp().isGlobal() } /** * Holds if this call to `replace` replaces `old` with `new`. diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected index ab45472d898..24e5f967f9f 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected @@ -23,11 +23,11 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | +| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | -| promises.js:11:22:11:31 | "resolved" | promises.js:27:16:27:16 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | -| promises.js:12:22:12:31 | "rejected" | promises.js:27:16:27:16 | v | +| promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | | properties.js:2:16:2:24 | "tainted" | properties.js:5:14:5:23 | a.someProp | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected b/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected index 67db1a56cd9..5c252b4aeea 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/TaintTracking.expected @@ -30,10 +30,8 @@ | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | | promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | -| promises.js:11:22:11:31 | "resolved" | promises.js:27:16:27:16 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | -| promises.js:12:22:12:31 | "rejected" | promises.js:27:16:27:16 | v | | promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/promises.js b/javascript/ql/test/library-tests/InterProceduralFlow/promises.js index 2f67ea30194..b0055c78781 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/promises.js +++ b/javascript/ql/test/library-tests/InterProceduralFlow/promises.js @@ -23,7 +23,7 @@ promise2.catch((v) => { var rej_sink = v; }); - promise2.finally((v) => { + promise2.finally((v) => { // no promise implementation sends an argument to the finally handler. So there is no data-flow here. var sink = v; }); diff --git a/javascript/ql/test/library-tests/Promises/Flowsteps.qll b/javascript/ql/test/library-tests/Promises/Flowsteps.qll new file mode 100644 index 00000000000..c18f87946ad --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/Flowsteps.qll @@ -0,0 +1,5 @@ +import javascript + +query predicate flowSteps(DataFlow::Node pred, DataFlow::Node succ) { + any(DataFlow::AdditionalFlowStep step).step(pred, succ) +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/flowsteps.js b/javascript/ql/test/library-tests/Promises/flowsteps.js new file mode 100644 index 00000000000..284008553c4 --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/flowsteps.js @@ -0,0 +1,56 @@ +(async function () { + function throws(resolve, reject) { + throw new Error() + } + new Promise(throws) + .catch((e) => console.log(e)); + + new Promise(throws) + .then((val) => console.log(val), (error) => console.log(error)); + + try { + await new Promise(throws); + } catch (e2) { + console.log(e2); + } + + new Promise((resolve, reject) => reject(3)) + .catch((e3) => console.log(e3)); + + try { + await new Promise((resolve, reject) => reject(4)); + } catch(e4) { + console.log(e4); + } + + new Promise(throws) + .then(() => {}) + .catch((e5) => console.log(e5)); + + + new Promise(throws) + .then(() => {}) + .catch((e6) => console.log(e6)) + .catch((e7) => console.log(e7)); + + var foo = await new Promise((resolve, reject) => resolve(8)) + + var bar = await new Promise((resolve, reject) => resolve(9)).then((x) => x + 2); + + var p = Promise.resolve(3); + var baz = await p.then((val) => val * 2); + + var p2 = new Promise((resolve, reject) => { + if (Math.random() > 0.5) { + resolve(13); + } else { + reject(14); + } + }); + var quz = await p2.then(val => val * 4).catch(e => e * 3); + + function returnsPromise() { + return Promise.resolve(3); + } + var a = await returnsPromise(); +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 144b9e19036..a7351c85a13 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -1,12 +1,27 @@ test_ResolvedPromiseDefinition +| flowsteps.js:40:11:40:28 | Promise.resolve(3) | flowsteps.js:40:27:40:27 | 3 | +| flowsteps.js:53:12:53:29 | Promise.resolve(3) | flowsteps.js:53:28:53:28 | 3 | | promises.js:53:19:53:41 | Promise ... source) | promises.js:53:35:53:40 | source | | promises.js:62:19:62:41 | Promise ... source) | promises.js:62:35:62:40 | source | | promises.js:71:5:71:27 | Promise ... source) | promises.js:71:21:71:26 | source | test_PromiseDefinition_getARejectHandler +| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:6:12:6:32 | (e) => ... .log(e) | +| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:9:38:9:66 | (error) ... (error) | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:18:12:18:34 | (e3) => ... log(e3) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:20:6:22:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | test_PromiseDefinition_getExecutor +| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | +| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | +| flowsteps.js:12:11:12:29 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:17:15:17:44 | (resolv ... ject(3) | +| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | flowsteps.js:21:23:21:52 | (resolv ... ject(4) | +| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | +| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | +| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | flowsteps.js:36:31:36:61 | (resolv ... olve(8) | +| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:31:38:61 | (resolv ... olve(9) | +| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:43:24:49:3 | (resolv ... }\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:31:35:5 | functio ... ;\\n } | @@ -14,25 +29,103 @@ test_PromiseDefinition_getExecutor test_PromiseDefinition_getAFinallyHandler | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | test_PromiseDefinition +| flowsteps.js:5:3:5:21 | new Promise(throws) | +| flowsteps.js:8:3:8:21 | new Promise(throws) | +| flowsteps.js:12:11:12:29 | new Promise(throws) | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | +| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | +| flowsteps.js:26:3:26:21 | new Promise(throws) | +| flowsteps.js:31:3:31:21 | new Promise(throws) | +| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | +| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | +| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | | promises.js:33:19:35:6 | new Pro ... \\n }) | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | test_PromiseDefinition_getAResolveHandler +| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:9:11:9:35 | (val) = ... og(val) | +| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:27:11:27:18 | () => {} | +| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:32:11:32:18 | () => {} | +| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:69:38:80 | (x) => x + 2 | +| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:50:27:50:40 | val => val * 4 | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:36:18:38:5 | functio ... ;\\n } | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:46:18:48:5 | functio ... ;\\n } | test_PromiseDefinition_getRejectParameter +| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | +| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | +| flowsteps.js:12:11:12:29 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:17:25:17:30 | reject | +| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | flowsteps.js:21:33:21:38 | reject | +| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | +| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | +| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | flowsteps.js:36:41:36:46 | reject | +| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:41:38:46 | reject | +| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:43:34:43:39 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:50:33:55 | reject | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:48:43:53 | reject | test_PromiseDefinition_getResolveParameter +| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | +| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | +| flowsteps.js:12:11:12:29 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:17:16:17:22 | resolve | +| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | flowsteps.js:21:24:21:30 | resolve | +| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | +| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | +| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | flowsteps.js:36:32:36:38 | resolve | +| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:32:38:38 | resolve | +| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:43:25:43:31 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:41:33:47 | resolve | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:39:43:45 | resolve | test_PromiseDefinition_getACatchHandler +| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:6:12:6:32 | (e) => ... .log(e) | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:18:12:18:34 | (e3) => ... log(e3) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | +flowSteps +| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:6:13:6:13 | e | +| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:9:39:9:43 | error | +| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:13:12:13:13 | e2 | +| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:28:13:28:14 | e5 | +| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:33:13:33:14 | e6 | +| flowsteps.js:17:15:17:44 | exceptional return of anonymous function | flowsteps.js:18:13:18:14 | e3 | +| flowsteps.js:17:43:17:43 | 3 | flowsteps.js:18:13:18:14 | e3 | +| flowsteps.js:21:23:21:52 | exceptional return of anonymous function | flowsteps.js:22:11:22:12 | e4 | +| flowsteps.js:21:51:21:51 | 4 | flowsteps.js:22:11:22:12 | e4 | +| flowsteps.js:27:11:27:18 | exceptional return of anonymous function | flowsteps.js:28:13:28:14 | e5 | +| flowsteps.js:32:11:32:18 | exceptional return of anonymous function | flowsteps.js:33:13:33:14 | e6 | +| flowsteps.js:33:12:33:34 | exceptional return of anonymous function | flowsteps.js:34:13:34:14 | e7 | +| flowsteps.js:36:31:36:61 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | +| flowsteps.js:36:60:36:60 | 8 | flowsteps.js:36:13:36:62 | await n ... lve(8)) | +| flowsteps.js:38:31:38:61 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | +| flowsteps.js:38:60:38:60 | 9 | flowsteps.js:38:70:38:70 | x | +| flowsteps.js:38:69:38:80 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | +| flowsteps.js:38:76:38:80 | x + 2 | flowsteps.js:38:13:38:81 | await n ... x + 2) | +| flowsteps.js:40:27:40:27 | 3 | flowsteps.js:41:27:41:29 | val | +| flowsteps.js:41:26:41:41 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | +| flowsteps.js:41:35:41:41 | val * 2 | flowsteps.js:41:13:41:42 | await p ... al * 2) | +| flowsteps.js:43:24:49:3 | exceptional return of anonymous function | flowsteps.js:50:49:50:49 | e | +| flowsteps.js:45:12:45:13 | 13 | flowsteps.js:50:27:50:29 | val | +| flowsteps.js:47:11:47:12 | 14 | flowsteps.js:50:49:50:49 | e | +| flowsteps.js:50:27:50:40 | exceptional return of anonymous function | flowsteps.js:50:49:50:49 | e | +| flowsteps.js:50:34:50:40 | val * 4 | flowsteps.js:50:13:50:59 | await p ... e * 3) | +| flowsteps.js:50:49:50:58 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | +| flowsteps.js:50:54:50:58 | e * 3 | flowsteps.js:50:13:50:59 | await p ... e * 3) | +| flowsteps.js:53:28:53:28 | 3 | flowsteps.js:55:11:55:32 | await r ... omise() | +| promises.js:4:13:4:18 | source | promises.js:6:26:6:28 | val | +| promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:20:7:20:7 | v | +| promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:23:19:23:19 | v | +| promises.js:14:11:14:20 | res_source | promises.js:18:18:18:18 | v | +| promises.js:16:11:16:20 | rej_source | promises.js:20:7:20:7 | v | +| promises.js:16:11:16:20 | rej_source | promises.js:23:19:23:19 | v | +| promises.js:34:17:34:22 | source | promises.js:36:28:36:30 | val | +| promises.js:44:17:44:22 | source | promises.js:46:28:46:30 | val | +| promises.js:53:35:53:40 | source | promises.js:54:28:54:30 | val | +| promises.js:62:35:62:40 | source | promises.js:63:28:63:30 | val | +| promises.js:71:21:71:26 | source | promises.js:71:34:71:36 | val | diff --git a/javascript/ql/test/library-tests/Promises/tests.ql b/javascript/ql/test/library-tests/Promises/tests.ql index 6b29dc01fa3..b2ff77cd0a1 100644 --- a/javascript/ql/test/library-tests/Promises/tests.ql +++ b/javascript/ql/test/library-tests/Promises/tests.ql @@ -7,3 +7,4 @@ import PromiseDefinition_getAResolveHandler import PromiseDefinition_getRejectParameter import PromiseDefinition_getResolveParameter import PromiseDefinition_getACatchHandler +import Flowsteps \ No newline at end of file From c50de3a7e81982ea99bde25f6b377088f2546c6c Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 10 Jan 2020 17:49:24 +0100 Subject: [PATCH 004/148] update expected output of tests --- .../InterProceduralFlow/GermanFlow.expected | 4 ++-- .../Promises/AdditionalPromises.expected | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected index fef4bafb3fd..9c3980797ea 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected @@ -24,11 +24,11 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | +| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | -| promises.js:11:22:11:31 | "resolved" | promises.js:27:16:27:16 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | -| promises.js:12:22:12:31 | "rejected" | promises.js:27:16:27:16 | v | +| promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | | properties.js:2:16:2:24 | "tainted" | properties.js:5:14:5:23 | a.someProp | diff --git a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index a3fa5795b30..7e2e94c3454 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -1,4 +1,19 @@ | additional-promises.js:2:13:2:57 | new Pin ... ct) {}) | +| flowsteps.js:5:3:5:21 | new Promise(throws) | +| flowsteps.js:8:3:8:21 | new Promise(throws) | +| flowsteps.js:12:11:12:29 | new Promise(throws) | +| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | +| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | +| flowsteps.js:26:3:26:21 | new Promise(throws) | +| flowsteps.js:26:3:27:19 | new Pro ... => {}) | +| flowsteps.js:31:3:31:21 | new Promise(throws) | +| flowsteps.js:31:3:32:19 | new Pro ... => {}) | +| flowsteps.js:31:3:33:35 | new Pro ... og(e6)) | +| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | +| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | +| flowsteps.js:40:11:40:28 | Promise.resolve(3) | +| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | +| flowsteps.js:50:19:50:41 | p2.then ... al * 4) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | | promises.js:33:19:35:6 | new Pro ... \\n }) | From 7570fa9137965f806e53792bc61855286f8daca1 Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Sat, 11 Jan 2020 21:55:54 +0100 Subject: [PATCH 005/148] Query to detect LDAP injections in Java JNDI and UnboundID sinks JNDI, UnboundID and Spring LDAP sanitizers --- .../src/Security/CWE/CWE-90/LdapInjection.ql | 23 ++ .../Security/CWE/CWE-90/LdapInjectionLib.qll | 238 ++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 java/ql/src/Security/CWE/CWE-90/LdapInjection.ql create mode 100644 java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql b/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql new file mode 100644 index 00000000000..3797b1191fc --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql @@ -0,0 +1,23 @@ +/** + * @name LDAP query built from user-controlled sources + * @description Building an LDAP query from user-controlled sources is vulnerable to insertion of + * malicious LDAP code by the user. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/ldap-injection + * @tags security + * external/cwe/cwe-090 + */ + +import semmle.code.java.Expr +import semmle.code.java.dataflow.FlowSources +import LdapInjectionLib +import DataFlow::PathGraph + +from + DataFlow::PathNode source, DataFlow::PathNode sink, LdapInjectionFlowConfig conf +where conf.hasFlowPath(source, sink) +// select sink.getNode(), source, sink, "LDAP query might include code from $@.", source.getNode(), +// "this user input", +select source, sink, sink.getNode().getEnclosingCallable().getName(), sink.getNode().getLocation().getStartLine() diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll new file mode 100644 index 00000000000..d48c35235fa --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll @@ -0,0 +1,238 @@ +import java +import semmle.code.java.dataflow.FlowSources +import DataFlow + +/** The class `com.unboundid.ldap.sdk.SearchRequest`. */ +class TypeSearchRequest extends Class { + TypeSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") } +} + +/** The class `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */ +class TypeReadOnlySearchRequest extends Interface { + TypeReadOnlySearchRequest() { + this.hasQualifiedName("com.unboundid.ldap.sdk", "ReadOnlySearchRequest") + } +} + +/** The class `com.unboundid.ldap.sdk.Filter`. */ +class TypeFilter extends Class { + TypeFilter() { this.hasQualifiedName("com.unboundid.ldap.sdk", "Filter") } +} + +/** The class `com.unboundid.ldap.sdk.LDAPConnection`. */ +class TypeLDAPConnection extends Class { + TypeLDAPConnection() { this.hasQualifiedName("com.unboundid.ldap.sdk", "LDAPConnection") } +} + +/** The class `org.springframework.ldap.support.LdapEncoder`. */ +class TypeLdapEncoder extends Class { + TypeLdapEncoder() { this.hasQualifiedName("org.springframework.ldap.support", "LdapEncoder") } +} + +/** A data flow source for unvalidated user input that is used to construct LDAP queries. */ +abstract class LdapInjectionSource extends DataFlow::Node { } + +/** A data flow sink for unvalidated user input that is used to construct LDAP queries. */ +abstract class LdapInjectionSink extends DataFlow::ExprNode { } + +/** A sanitizer for unvalidated user input that is used to construct LDAP queries. */ +abstract class LdapInjectionSanitizer extends DataFlow::ExprNode { } + +/** +* A taint-tracking configuration for unvalidated user input that is used to construct LDAP queries. +*/ +class LdapInjectionFlowConfig extends TaintTracking::Configuration { + LdapInjectionFlowConfig() { this = "LdapInjectionFlowConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof LdapInjectionSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof LdapInjectionSink } + + override predicate isSanitizer(DataFlow::Node node) { node instanceof LdapInjectionSanitizer } + + override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { + filterStep(node1, node2) or searchRequestStep(node1, node2) + } +} + +/** A source of remote user input. */ +class RemoteSource extends LdapInjectionSource { + RemoteSource() { this instanceof RemoteFlowSource } +} + +/** A source of local user input. */ +class LocalSource extends LdapInjectionSource { + LocalSource() { this instanceof LocalUserInput } +} + +abstract class Context extends RefType { } + +/** + * The interface `javax.naming.directory.DirContext` or + * the class `javax.naming.directory.InitialDirContext`. + */ +class DirContext extends Context { + DirContext() { + this.hasQualifiedName("javax.naming.directory", "DirContext") or + this.hasQualifiedName("javax.naming.directory", "InitialDirContext") + } +} + +/** + * The interface `javax.naming.ldap.LdapContext` or + * the class `javax.naming.ldap.InitialLdapContext`. + */ +class LdapContext extends Context { + LdapContext() { + this.hasQualifiedName("javax.naming.ldap", "LdapContext") or + this.hasQualifiedName("javax.naming.ldap", "InitialLdapContext") + } +} + +/** + * JNDI sink for LDAP injection vulnerabilities, i.e. 2nd argument to search method from + * DirContext, InitialDirContext, LdapContext or InitialLdapContext. + */ +class JndiLdapInjectionSink extends LdapInjectionSink { + JndiLdapInjectionSink() { + exists(MethodAccess ma, Method m, int index | + ma.getMethod() = m and + ma.getArgument(index) = this.getExpr() + | + m.getDeclaringType() instanceof Context and m.hasName("search") and index = 1 + ) + } +} + +/** + * UnboundID sink for LDAP injection vulnerabilities, + * i.e. LDAPConnection.search or LDAPConnection.searchForEntry method. + */ +class UnboundIdLdapInjectionSink extends LdapInjectionSink { + UnboundIdLdapInjectionSink() { + exists(MethodAccess ma, Method m, int index, RefType argType | + ma.getMethod() = m and + ma.getArgument(index) = this.getExpr() and + ma.getArgument(index).getType() = argType + | + // LDAPConnection.search or LDAPConnection.searchForEntry method + m.getDeclaringType() instanceof TypeLDAPConnection and + (m.hasName("search") or m.hasName("searchForEntry")) and + ( + // Parameter type is SearchRequest or ReadOnlySearchRequest + ( + argType instanceof TypeReadOnlySearchRequest or + argType instanceof TypeSearchRequest + ) or + // Or parameter index is 2, 3, 5, 6 or 7 (this is where filter parameter is) + // but it's not the last one nor beyond the last one (varargs representing attributes) + index = any(int i | + (i = [2..3] or i = [5..7]) and i < ma.getMethod().getNumberOfParameters() - 1 + ) + ) + ) + } +} + +/** + * Spring LDAP sink for LDAP injection vulnerabilities, + * i.e. LDAPConnection.search or LDAPConnection.searchForEntry method. + */ + // LdapTemplate: + // find(LdapQuery query, Class clazz) + // find(Name base, Filter filter, SearchControls searchControls, Class clazz) + // findOne(LdapQuery query, Class clazz) + // search - 2nd param if String (filter) + // search - 1st param if LdapQuery + // searchForContext(LdapQuery query) + // searchForObject - 2nd param if String (filter) + // searchForObject - 1st param if LdapQuery +class SpringLdapInjectionSink extends LdapInjectionSink { + SpringLdapInjectionSink() { + exists(MethodAccess ma, Method m, int index, RefType argType | + ma.getMethod() = m and + ma.getArgument(index) = this.getExpr() and + ma.getArgument(index).getType() = argType + | + // LDAPConnection.search or LDAPConnection.searchForEntry method + m.getDeclaringType() instanceof TypeLDAPConnection and + (m.hasName("search") or m.hasName("searchForEntry")) and + ( + // Parameter type is SearchRequest or ReadOnlySearchRequest + ( + argType instanceof TypeReadOnlySearchRequest or + argType instanceof TypeSearchRequest + ) or + // Or parameter index is 2, 3, 5, 6 or 7 (this is where filter parameter is) + // but it's not the last one nor beyond the last one (varargs representing attributes) + index = any(int i | + (i = [2..3] or i = [5..7]) and i < ma.getMethod().getNumberOfParameters() - 1 + ) + ) + ) + } +} + +/** An expression node with a primitive type. */ +class PrimitiveTypeSanitizer extends LdapInjectionSanitizer { + PrimitiveTypeSanitizer() { this.getType() instanceof PrimitiveType } +} + +/** An expression node with a boxed type. */ +class BoxedTypeSanitizer extends LdapInjectionSanitizer { + BoxedTypeSanitizer() { this.getType() instanceof BoxedType } +} + +/** encodeForLDAP and encodeForDN from OWASP ESAPI. */ +class EsapiSanitizer extends LdapInjectionSanitizer { + EsapiSanitizer() { + this.getExpr().(MethodAccess).getMethod().hasName("encodeForLDAP") + } +} + +/** LdapEncoder.filterEncode and LdapEncoder.nameEncode from Spring LDAP. */ +class SpringLdapSanitizer extends LdapInjectionSanitizer { + SpringLdapSanitizer() { + this.getType() instanceof TypeLdapEncoder and + this.getExpr().(MethodAccess).getMethod().hasName("filterEncode") + } +} + +/** Filter.encodeValue from UnboundID. */ +class UnboundIdSanitizer extends LdapInjectionSanitizer { + UnboundIdSanitizer() { + this.getType() instanceof TypeFilter and + this.getExpr().(MethodAccess).getMethod().hasName("encodeValue") + } +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID `Filter`, + * i.e. `Filter.create(tainted)`. + */ +predicate filterStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() or + n1.asExpr() = ma.getAnArgument() + | + n2.asExpr() = ma and + ma.getMethod() = m and + m.getDeclaringType() instanceof TypeFilter and + m.hasName("create") + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID + * `SearchRequest`, i.e. `new SearchRequest([...], tainted, [...])`, where `tainted` is + * parameter number 3, 4, 7, 8 or 9, but not the last one or beyond the last one (varargs). + */ +predicate searchRequestStep(ExprNode n1, ExprNode n2) { + exists(ConstructorCall cc, int index | cc.getConstructedType() instanceof TypeSearchRequest | + n1.asExpr() = cc.getArgument(index) and + n2.asExpr() = cc and + index = any(int i | + (i = [2..3] or i = [6..8]) and i < cc.getConstructor().getNumberOfParameters() - 1 + ) + ) +} \ No newline at end of file From c01aa3d2ee465309d9310478dcceedbb61a3dc02 Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Sun, 12 Jan 2020 13:28:29 +0100 Subject: [PATCH 006/148] Query to detect LDAP injections in Java Spring LDAP sink --- .../Security/CWE/CWE-90/LdapInjectionLib.qll | 151 ++++++++++++------ 1 file changed, 105 insertions(+), 46 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll index d48c35235fa..df283841e5b 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll @@ -7,7 +7,7 @@ class TypeSearchRequest extends Class { TypeSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") } } -/** The class `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */ +/** The interface `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */ class TypeReadOnlySearchRequest extends Interface { TypeReadOnlySearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "ReadOnlySearchRequest") @@ -15,8 +15,8 @@ class TypeReadOnlySearchRequest extends Interface { } /** The class `com.unboundid.ldap.sdk.Filter`. */ -class TypeFilter extends Class { - TypeFilter() { this.hasQualifiedName("com.unboundid.ldap.sdk", "Filter") } +class TypeUnboundIdLdapFilter extends Class { + TypeUnboundIdLdapFilter() { this.hasQualifiedName("com.unboundid.ldap.sdk", "Filter") } } /** The class `com.unboundid.ldap.sdk.LDAPConnection`. */ @@ -24,11 +24,46 @@ class TypeLDAPConnection extends Class { TypeLDAPConnection() { this.hasQualifiedName("com.unboundid.ldap.sdk", "LDAPConnection") } } +/** The class `org.springframework.ldap.core.LdapTemplate`. */ +class TypeLdapTemplate extends Class { + TypeLdapTemplate() { this.hasQualifiedName("org.springframework.ldap.core", "LdapTemplate") } +} + +/** The interface `org.springframework.ldap.query.LdapQuery`. */ +class TypeLdapQuery extends Interface { + TypeLdapQuery() { this.hasQualifiedName("org.springframework.ldap.query", "LdapQuery") } +} + +/** The interface `org.springframework.ldap.query.LdapQueryBuilder`. */ +class TypeLdapQueryBuilder extends Class { + TypeLdapQueryBuilder() { + this.hasQualifiedName("org.springframework.ldap.query", "LdapQueryBuilder") + } +} + +/** The interface `org.springframework.ldap.filter.HardcodedFilter`. */ +class TypeHardcodedFilter extends Class { + TypeHardcodedFilter() { + this.hasQualifiedName("org.springframework.ldap.filter", "HardcodedFilter") + } +} + +/** The interface `org.springframework.ldap.filter.Filter`. */ +class TypeSpringLdapFilter extends Interface { + TypeSpringLdapFilter() { this.hasQualifiedName("org.springframework.ldap.filter", "Filter") } +} + /** The class `org.springframework.ldap.support.LdapEncoder`. */ class TypeLdapEncoder extends Class { TypeLdapEncoder() { this.hasQualifiedName("org.springframework.ldap.support", "LdapEncoder") } } +/** Holds if the parameter of `c` at index `paramIndex` is varargs. */ +bindingset[paramIndex] +predicate isVarargs(Callable c, int paramIndex) { + c.getParameter(min(int i | i = paramIndex or i = c.getNumberOfParameters() - 1 | i)).isVarargs() +} + /** A data flow source for unvalidated user input that is used to construct LDAP queries. */ abstract class LdapInjectionSource extends DataFlow::Node { } @@ -51,7 +86,10 @@ class LdapInjectionFlowConfig extends TaintTracking::Configuration { override predicate isSanitizer(DataFlow::Node node) { node instanceof LdapInjectionSanitizer } override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { - filterStep(node1, node2) or searchRequestStep(node1, node2) + filterStep(node1, node2) or + searchRequestStep(node1, node2) or + ldapQueryStep(node1, node2) or + hardcodedFilterStep(node1, node2) } } @@ -110,25 +148,22 @@ class JndiLdapInjectionSink extends LdapInjectionSink { */ class UnboundIdLdapInjectionSink extends LdapInjectionSink { UnboundIdLdapInjectionSink() { - exists(MethodAccess ma, Method m, int index, RefType argType | + exists(MethodAccess ma, Method m, int index, Parameter param | ma.getMethod() = m and ma.getArgument(index) = this.getExpr() and - ma.getArgument(index).getType() = argType + m.getParameter(index) = param | // LDAPConnection.search or LDAPConnection.searchForEntry method m.getDeclaringType() instanceof TypeLDAPConnection and (m.hasName("search") or m.hasName("searchForEntry")) and + // Parameter is not varargs + not isVarargs(m, index) and ( // Parameter type is SearchRequest or ReadOnlySearchRequest - ( - argType instanceof TypeReadOnlySearchRequest or - argType instanceof TypeSearchRequest - ) or + param.getType() instanceof TypeReadOnlySearchRequest or + param.getType() instanceof TypeSearchRequest or // Or parameter index is 2, 3, 5, 6 or 7 (this is where filter parameter is) - // but it's not the last one nor beyond the last one (varargs representing attributes) - index = any(int i | - (i = [2..3] or i = [5..7]) and i < ma.getMethod().getNumberOfParameters() - 1 - ) + index = any(int i | i = [2..3] or i = [5..7]) ) ) } @@ -136,38 +171,31 @@ class UnboundIdLdapInjectionSink extends LdapInjectionSink { /** * Spring LDAP sink for LDAP injection vulnerabilities, - * i.e. LDAPConnection.search or LDAPConnection.searchForEntry method. + * i.e. LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method. */ - // LdapTemplate: - // find(LdapQuery query, Class clazz) - // find(Name base, Filter filter, SearchControls searchControls, Class clazz) - // findOne(LdapQuery query, Class clazz) - // search - 2nd param if String (filter) - // search - 1st param if LdapQuery - // searchForContext(LdapQuery query) - // searchForObject - 2nd param if String (filter) - // searchForObject - 1st param if LdapQuery class SpringLdapInjectionSink extends LdapInjectionSink { SpringLdapInjectionSink() { - exists(MethodAccess ma, Method m, int index, RefType argType | + exists(MethodAccess ma, Method m, int index, RefType paramType | ma.getMethod() = m and ma.getArgument(index) = this.getExpr() and - ma.getArgument(index).getType() = argType + m.getParameterType(index) = paramType | - // LDAPConnection.search or LDAPConnection.searchForEntry method - m.getDeclaringType() instanceof TypeLDAPConnection and - (m.hasName("search") or m.hasName("searchForEntry")) and + // LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method + m.getDeclaringType() instanceof TypeLdapTemplate and ( - // Parameter type is SearchRequest or ReadOnlySearchRequest - ( - argType instanceof TypeReadOnlySearchRequest or - argType instanceof TypeSearchRequest - ) or - // Or parameter index is 2, 3, 5, 6 or 7 (this is where filter parameter is) - // but it's not the last one nor beyond the last one (varargs representing attributes) - index = any(int i | - (i = [2..3] or i = [5..7]) and i < ma.getMethod().getNumberOfParameters() - 1 - ) + m.hasName("authenticate") or + m.hasName("find") or + m.hasName("findOne") or + m.hasName("search") or + m.hasName("searchForContext") or + m.hasName("searchForObject") + ) and + ( + // Parameter type is LdapQuery or Filter + paramType instanceof TypeLdapQuery or + paramType instanceof TypeSpringLdapFilter or + // Or parameter index is 1 (this is where filter parameter is) + index = 1 ) ) } @@ -201,7 +229,7 @@ class SpringLdapSanitizer extends LdapInjectionSanitizer { /** Filter.encodeValue from UnboundID. */ class UnboundIdSanitizer extends LdapInjectionSanitizer { UnboundIdSanitizer() { - this.getType() instanceof TypeFilter and + this.getType() instanceof TypeUnboundIdLdapFilter and this.getExpr().(MethodAccess).getMethod().hasName("encodeValue") } } @@ -217,7 +245,7 @@ predicate filterStep(ExprNode n1, ExprNode n2) { | n2.asExpr() = ma and ma.getMethod() = m and - m.getDeclaringType() instanceof TypeFilter and + m.getDeclaringType() instanceof TypeUnboundIdLdapFilter and m.hasName("create") ) } @@ -225,14 +253,45 @@ predicate filterStep(ExprNode n1, ExprNode n2) { /** * Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID * `SearchRequest`, i.e. `new SearchRequest([...], tainted, [...])`, where `tainted` is - * parameter number 3, 4, 7, 8 or 9, but not the last one or beyond the last one (varargs). + * parameter number 3, 4, 7, 8 or 9, but is not varargs. */ predicate searchRequestStep(ExprNode n1, ExprNode n2) { - exists(ConstructorCall cc, int index | cc.getConstructedType() instanceof TypeSearchRequest | + exists(ConstructorCall cc, Constructor c, int index | + cc.getConstructedType() instanceof TypeSearchRequest + | n1.asExpr() = cc.getArgument(index) and n2.asExpr() = cc and - index = any(int i | - (i = [2..3] or i = [6..8]) and i < cc.getConstructor().getNumberOfParameters() - 1 - ) + c = cc.getConstructor() and + // not c.getParameter(min(int i | i = index or i = c.getNumberOfParameters() - 1 | i)).isVarargs() and + not isVarargs(c, index) and + index = any(int i |i = [2..3] or i = [6..8]) + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring `LdapQuery`, + * i.e. `LdapQueryBuilder.query().filter(tainted)`. + */ +predicate ldapQueryStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m, int index | + n1.asExpr() = ma.getQualifier() or + n1.asExpr() = ma.getArgument(index) + | + n2.asExpr() = ma and + ma.getMethod() = m and + m.getDeclaringType() instanceof TypeLdapQueryBuilder and + m.hasName("filter") and + index = 0 + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring + * `HardcodedFilter`, i.e. `new HardcodedFilter(tainted)`. + */ +predicate hardcodedFilterStep(ExprNode n1, ExprNode n2) { + exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeHardcodedFilter | + n1.asExpr() = cc.getAnArgument() and + n2.asExpr() = cc ) } \ No newline at end of file From 3e86dd11825157c0d2ad7ca4d4aeb01950876645 Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Sun, 12 Jan 2020 20:19:25 +0100 Subject: [PATCH 007/148] Query to detect LDAP injections in Java Apache LDAP API sink --- .../src/Security/CWE/CWE-90/LdapInjection.ql | 5 +- .../Security/CWE/CWE-90/LdapInjectionLib.qll | 108 ++++++++++++------ 2 files changed, 76 insertions(+), 37 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql b/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql index 3797b1191fc..be247b58e57 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql @@ -18,6 +18,5 @@ import DataFlow::PathGraph from DataFlow::PathNode source, DataFlow::PathNode sink, LdapInjectionFlowConfig conf where conf.hasFlowPath(source, sink) -// select sink.getNode(), source, sink, "LDAP query might include code from $@.", source.getNode(), -// "this user input", -select source, sink, sink.getNode().getEnclosingCallable().getName(), sink.getNode().getLocation().getStartLine() +select sink.getNode(), source, sink, "LDAP query might include code from $@.", source.getNode(), + "this user input" diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll index df283841e5b..84dba816a27 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll @@ -2,9 +2,19 @@ import java import semmle.code.java.dataflow.FlowSources import DataFlow +/** The interface `javax.naming.directory.DirContext`. */ +class TypeDirContext extends Interface { + TypeDirContext() { this.hasQualifiedName("javax.naming.directory", "DirContext") } +} + +/** The interface `javax.naming.ldap.LdapContext`. */ +class TypeLdapContext extends Interface { + TypeLdapContext() { this.hasQualifiedName("javax.naming.ldap", "LdapContext") } +} + /** The class `com.unboundid.ldap.sdk.SearchRequest`. */ -class TypeSearchRequest extends Class { - TypeSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") } +class TypeUnboundIdSearchRequest extends Class { + TypeUnboundIdSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") } } /** The interface `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */ @@ -53,6 +63,20 @@ class TypeSpringLdapFilter extends Interface { TypeSpringLdapFilter() { this.hasQualifiedName("org.springframework.ldap.filter", "Filter") } } +/** The interface `org.apache.directory.ldap.client.api.LdapConnection`. */ +class TypeLdapConnection extends Interface { + TypeLdapConnection() { + this.hasQualifiedName("org.apache.directory.ldap.client.api", "LdapConnection") + } +} + +/** The interface `org.apache.directory.api.ldap.model.message.SearchRequest`. */ +class TypeApacheSearchRequest extends Interface { + TypeApacheSearchRequest() { + this.hasQualifiedName("org.apache.directory.api.ldap.model.message", "SearchRequest") + } +} + /** The class `org.springframework.ldap.support.LdapEncoder`. */ class TypeLdapEncoder extends Class { TypeLdapEncoder() { this.hasQualifiedName("org.springframework.ldap.support", "LdapEncoder") } @@ -87,9 +111,10 @@ class LdapInjectionFlowConfig extends TaintTracking::Configuration { override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { filterStep(node1, node2) or - searchRequestStep(node1, node2) or + unboundIdSearchRequestStep(node1, node2) or ldapQueryStep(node1, node2) or - hardcodedFilterStep(node1, node2) + hardcodedFilterStep(node1, node2) or + apacheSearchRequestStep(node1, node2) } } @@ -103,30 +128,6 @@ class LocalSource extends LdapInjectionSource { LocalSource() { this instanceof LocalUserInput } } -abstract class Context extends RefType { } - -/** - * The interface `javax.naming.directory.DirContext` or - * the class `javax.naming.directory.InitialDirContext`. - */ -class DirContext extends Context { - DirContext() { - this.hasQualifiedName("javax.naming.directory", "DirContext") or - this.hasQualifiedName("javax.naming.directory", "InitialDirContext") - } -} - -/** - * The interface `javax.naming.ldap.LdapContext` or - * the class `javax.naming.ldap.InitialLdapContext`. - */ -class LdapContext extends Context { - LdapContext() { - this.hasQualifiedName("javax.naming.ldap", "LdapContext") or - this.hasQualifiedName("javax.naming.ldap", "InitialLdapContext") - } -} - /** * JNDI sink for LDAP injection vulnerabilities, i.e. 2nd argument to search method from * DirContext, InitialDirContext, LdapContext or InitialLdapContext. @@ -137,7 +138,12 @@ class JndiLdapInjectionSink extends LdapInjectionSink { ma.getMethod() = m and ma.getArgument(index) = this.getExpr() | - m.getDeclaringType() instanceof Context and m.hasName("search") and index = 1 + ( + m.getDeclaringType().getAnAncestor() instanceof TypeDirContext or + m.getDeclaringType().getAnAncestor() instanceof TypeLdapContext + ) and + m.hasName("search") and + index = 1 ) } } @@ -161,7 +167,7 @@ class UnboundIdLdapInjectionSink extends LdapInjectionSink { ( // Parameter type is SearchRequest or ReadOnlySearchRequest param.getType() instanceof TypeReadOnlySearchRequest or - param.getType() instanceof TypeSearchRequest or + param.getType() instanceof TypeUnboundIdSearchRequest or // Or parameter index is 2, 3, 5, 6 or 7 (this is where filter parameter is) index = any(int i | i = [2..3] or i = [5..7]) ) @@ -201,6 +207,27 @@ class SpringLdapInjectionSink extends LdapInjectionSink { } } +/** Apache LDAP API sink for LDAP injection vulnerabilities, i.e. LdapConnection.search method. */ +class ApacheLdapInjectionSink extends LdapInjectionSink { + ApacheLdapInjectionSink() { + exists(MethodAccess ma, Method m, int index, RefType paramType | + ma.getMethod() = m and + ma.getArgument(index) = this.getExpr() and + m.getParameterType(index) = paramType + | + // LdapConnection.search method + m.getDeclaringType().getAnAncestor() instanceof TypeLdapConnection and + m.hasName("search") and + ( + // Parameter type is SearchRequest + paramType instanceof TypeApacheSearchRequest or + // Or parameter index is 1 (this is where filter parameter is) + index = 1 + ) + ) + } +} + /** An expression node with a primitive type. */ class PrimitiveTypeSanitizer extends LdapInjectionSanitizer { PrimitiveTypeSanitizer() { this.getType() instanceof PrimitiveType } @@ -240,7 +267,6 @@ class UnboundIdSanitizer extends LdapInjectionSanitizer { */ predicate filterStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m | - n1.asExpr() = ma.getQualifier() or n1.asExpr() = ma.getAnArgument() | n2.asExpr() = ma and @@ -255,9 +281,9 @@ predicate filterStep(ExprNode n1, ExprNode n2) { * `SearchRequest`, i.e. `new SearchRequest([...], tainted, [...])`, where `tainted` is * parameter number 3, 4, 7, 8 or 9, but is not varargs. */ -predicate searchRequestStep(ExprNode n1, ExprNode n2) { +predicate unboundIdSearchRequestStep(ExprNode n1, ExprNode n2) { exists(ConstructorCall cc, Constructor c, int index | - cc.getConstructedType() instanceof TypeSearchRequest + cc.getConstructedType() instanceof TypeUnboundIdSearchRequest | n1.asExpr() = cc.getArgument(index) and n2.asExpr() = cc and @@ -274,7 +300,6 @@ predicate searchRequestStep(ExprNode n1, ExprNode n2) { */ predicate ldapQueryStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m, int index | - n1.asExpr() = ma.getQualifier() or n1.asExpr() = ma.getArgument(index) | n2.asExpr() = ma and @@ -294,4 +319,19 @@ predicate hardcodedFilterStep(ExprNode n1, ExprNode n2) { n1.asExpr() = cc.getAnArgument() and n2.asExpr() = cc ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Apache LDAP API + * `SearchRequest`, i.e. `SearchRequest s = new SearchRequestImpl(); s.setFilter(tainted");`. + */ +predicate apacheSearchRequestStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getAnArgument() + | + n2.asExpr() = ma.getQualifier() and + ma.getMethod() = m and + m.getDeclaringType().getAnAncestor() instanceof TypeApacheSearchRequest and + m.hasName("setFilter") + ) } \ No newline at end of file From d09bce5cd7f42b0ec6f4791878e8d7397ac9e5b9 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 13 Jan 2020 20:06:51 +0100 Subject: [PATCH 008/148] custom load/store steps to implement promise flow --- .../ql/src/semmle/javascript/Promises.qll | 285 +++++++----------- .../javascript/dataflow/Configuration.qll | 76 ++++- .../test/library-tests/Promises/Flowsteps.qll | 5 - .../ql/test/library-tests/Promises/flow.js | 49 +++ .../ql/test/library-tests/Promises/flow.qll | 17 ++ .../test/library-tests/Promises/flowsteps.js | 56 ---- .../ql/test/library-tests/Promises/options | 1 + .../library-tests/Promises/tests.expected | 160 ++++------ .../ql/test/library-tests/Promises/tests.ql | 2 +- 9 files changed, 322 insertions(+), 329 deletions(-) delete mode 100644 javascript/ql/test/library-tests/Promises/Flowsteps.qll create mode 100644 javascript/ql/test/library-tests/Promises/flow.js create mode 100644 javascript/ql/test/library-tests/Promises/flow.qll delete mode 100644 javascript/ql/test/library-tests/Promises/flowsteps.js create mode 100644 javascript/ql/test/library-tests/Promises/options diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index ee4b42addcc..f018724964f 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -125,216 +125,159 @@ class AggregateES2015PromiseDefinition extends PromiseCreationCall { */ private module PromiseFlow { /** - * A promise from which data-flow can flow into or out of. - * - * This promise can both be a promise created by e.g. `new Promise(..)` or `Promise.resolve(..)`, - * or the result from calling a method on a promise e.g. `promise.then(..)`. - * - * The 4 methods in this class describe that ordinary and exceptional flow can flow into and out of this promise. + * Gets the pseudo-field used to describe resolved values in a promise. */ - private abstract class PromiseNode extends DataFlow::SourceNode { - - /** - * Get a DataFlow::Node for a value that this promise is resolved with. - * The value is sent either to a chained promise, or to an `await` expression. - * - * The value is e.g. an argument to `resolve(..)`, or a return value from a `.then(..)` handler. - */ - DataFlow::Node getASentResolveValue() { none() } - - /** - * Get the DataFlow::Node that receives the value that this promise has been resolved with. - * - * E.g. the `x` in `promise.then((x) => ..)`. - */ - DataFlow::Node getReceivedResolveValue() { none() } - - /** - * Get a DataFlow::Node for a value that this promise is rejected with. - * The value is sent either to a chained promise, or thrown by an `await` expression. - * - * The value is e.g. an argument to `reject(..)`, or an exception thrown by the promise executor. - */ - DataFlow::Node getASentRejectValue() { none() } - - /** - * Get the DataFlow::Node that receives the value that this promise has been rejected with. - * - * E.g. the `x` in `promise.catch((x) => ..)`. - */ - DataFlow::Node getReceivedRejectValue() { none() } + string resolveField() { + result = "$PromiseResolveField$" } - + /** - * A PromiseNode for a PromiseDefinition. - * E.g. `new Promise(..)`. + * Gets the pseudo-field used to describe rejected values in a promise. */ - private class PromiseDefinitionNode extends PromiseNode { + string rejectField() { + result = "$PromiseRejectField$" + } + + /** + * A flow step describing a promise definition. + * + * The resolved/rejected value is written to a pseudo-field on the promise. + */ + class PromiseDefitionStep extends DataFlow::AdditionalFlowStep { PromiseDefinition promise; - - PromiseDefinitionNode() { this = promise } - - override DataFlow::Node getASentResolveValue() { - result = promise.getResolveParameter().getACall().getArgument(0) + PromiseDefitionStep() { + this = promise } - override DataFlow::Node getASentRejectValue() { - result = promise.getRejectParameter().getACall().getArgument(0) + override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = promise.getResolveParameter().getACall().getArgument(0) and + succ = this or - result = promise.getExecutor().getExceptionalReturn() + prop = rejectField() and + ( + pred = promise.getRejectParameter().getACall().getArgument(0) or + pred = promise.getExecutor().getExceptionalReturn() + ) and + succ = this } } /** - * A PromiseNode for a call that creates a promise. - * E.g. `Promise.resolve(..)` or `Promise.all(..)`. + * A flow step describing the a Promise.resolve (and similar) call. */ - private class PromiseCreationNode extends PromiseNode { + class CreationStep extends DataFlow::AdditionalFlowStep { PromiseCreationCall promise; - - PromiseCreationNode() { this = promise } - - override DataFlow::Node getASentResolveValue() { - exists(DataFlow::Node value | value = promise.getValue() | - not value instanceof PromiseNode and - result = value - or - result = value.(PromiseNode).getASentResolveValue() - ) + CreationStep() { + this = promise } - override DataFlow::Node getASentRejectValue() { - result = promise.getValue().(PromiseNode).getASentRejectValue() + override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = promise.getValue() and + succ = this } } /** - * A node referring to a PromiseNode through type-tracking. + * A load step loading the pseudo-field describing that the promise is either resolved or rejected. + * A resolved value is forwarding as the resulting value of the `await` expression, + * and a rejected value is thrown as a exception. */ - private class TrackedPromiseNode extends PromiseNode { - PromiseNode base; - TrackedPromiseNode() { - this = trackPromise(DataFlow::TypeTracker::end(), base) and - not this instanceof PromiseDefinitionNode and - not this instanceof PromiseCreationNode + class AwaitStep extends DataFlow::AdditionalFlowStep { + DataFlow::Node operand; + AwaitExpr await; + AwaitStep() { + this.getEnclosingExpr() = await and + operand.getEnclosingExpr() = await.getOperand() } - override DataFlow::Node getASentResolveValue() { result = base.getASentResolveValue() } - override DataFlow::Node getReceivedResolveValue() { result = base.getReceivedResolveValue() } - override DataFlow::Node getASentRejectValue() { result = base.getASentRejectValue() } - override DataFlow::Node getReceivedRejectValue() { result = base.getReceivedRejectValue() } + override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + succ = this and + pred = operand.getALocalSource() + or + prop = rejectField() and + succ = await.getExceptionTarget() and + pred = operand.getALocalSource() + } + } + + /** + * A flow step describing the data-flow related to the `.then` method of a promise. + */ + class ThenStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { + ThenStep() { + this.getMethodName() = "then" + } + + override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getReceiver().getALocalSource() and + succ = getCallback(0).getParameter(0) + or + prop = rejectField() and + pred = getReceiver().getALocalSource() and + succ = getCallback(1).getParameter(0) + } + + override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + not exists(this.getArgument(1)) and + prop = rejectField() and + pred = getReceiver().getALocalSource() and + succ = this + } + + override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getCallback([0..1]).getAReturn() and + succ = this + or + prop = rejectField() and + pred = getCallback([0..1]).getExceptionalReturn() and + succ = this + } } - private DataFlow::SourceNode trackPromise(DataFlow::TypeTracker t, PromiseNode promise) { - t.start() and result = promise - or - exists(DataFlow::TypeTracker t2 | result = trackPromise(t2, promise).track(t2, t)) - } - /** - * A PromiseNode that is a method call on an existing PromiseNode. - * E.g. `promise.then(..)`. + * A flow step describing the data-flow related to the `.catch` method of a promise. */ - private abstract class ChainedPromiseNode extends PromiseNode, DataFlow::MethodCallNode { - PromiseNode base; - - ChainedPromiseNode() { this = base.getAMethodCall(_) } - - PromiseNode getBase() { result = base } - } - - /** - * A PromiseNode for the `.then(..)` method on an existing promise. - */ - private class PromiseThenNode extends ChainedPromiseNode { - PromiseThenNode() { this = base.getAMethodCall("then") } - - override DataFlow::Node getASentResolveValue() { - exists(DataFlow::Node ret | ret = this.getCallback(0).getAReturn() | - if ret instanceof PromiseNode - then result = ret.(PromiseNode).getReceivedResolveValue() - else result = ret - ) + class CatchStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { + CatchStep() { + this.getMethodName() = "catch" } - override DataFlow::Node getASentRejectValue() { - not exists(this.getCallback(1)) and result = base.getASentRejectValue() - or - result = this.getCallback([0..1]).getExceptionalReturn() + override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = rejectField() and + pred = getReceiver().getALocalSource() and + succ = getCallback(0).getParameter(0) } - override DataFlow::Node getReceivedResolveValue() { result = this.getCallback(0).getParameter(0) } + override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getReceiver().getALocalSource() and + succ = this + } - override DataFlow::Node getReceivedRejectValue() { result = this.getCallback(1).getParameter(0) } - } - - /** - * A PromiseNode for the `.finally(..)` method on an existing promise. - */ - private class PromiseFinallyNode extends ChainedPromiseNode { - PromiseFinallyNode() { this = base.getAMethodCall("finally") } - - override DataFlow::Node getASentResolveValue() { result = base.getASentResolveValue() } - - override DataFlow::Node getASentRejectValue() { - result = base.getASentRejectValue() - or - result = this.getCallback(0).getExceptionalReturn() + override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = rejectField() and + pred = getCallback([0..1]).getExceptionalReturn() and + succ = this } } /** - * A PromiseNode for the `.catch(..)` method on an existing promise. + * A flow step describing the data-flow related to the `.finally` method of a promise. */ - private class PromiseCatchNode extends ChainedPromiseNode { - PromiseCatchNode() { this = base.getAMethodCall("catch") } - - override DataFlow::Node getASentResolveValue() { - exists(DataFlow::Node ret | ret = this.getCallback(0).getAReturn() | - if ret instanceof PromiseNode - then result = ret.(PromiseNode).getReceivedResolveValue() - else result = ret - ) - or - result = base.getASentResolveValue() + class FinallyStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { + FinallyStep() { + this.getMethodName() = "finally" } - override DataFlow::Node getASentRejectValue() { result = this.getCallback(0).getExceptionalReturn() } - - override DataFlow::Node getReceivedResolveValue() { none() } - - override DataFlow::Node getReceivedRejectValue() { result = this.getCallback(0).getParameter(0) } - } - - - private ChainedPromiseNode getAChainedPromise(PromiseNode p) { result.getBase() = p} - - /** - * A data flow edge from a promise resolve/reject to the corresponding handler (or `await` expression). - */ - private class PromiseFlowStep extends DataFlow::AdditionalFlowStep { - PromiseNode promise; - - PromiseFlowStep() { this = promise } - - override predicate step(DataFlow::Node pred, DataFlow::Node succ) { - pred = promise.getASentResolveValue() and - succ = getAChainedPromise(promise).getReceivedResolveValue() - or - pred = promise.getASentRejectValue() and - succ = getAChainedPromise(promise).getReceivedRejectValue() - or - pred = promise.getASentResolveValue() and - exists(DataFlow::SourceNode awaitNode | - awaitNode.asExpr().(AwaitExpr).getOperand() = promise.asExpr() and - succ = awaitNode - ) - or - pred = promise.getASentRejectValue() and - exists(DataFlow::SourceNode awaitNode | - awaitNode.asExpr().(AwaitExpr).getOperand() = promise.asExpr() and - succ = awaitNode.asExpr().getExceptionTarget() - ) + override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + (prop = resolveField() or prop = rejectField()) and + pred = getReceiver().getALocalSource() and + succ = this } } } diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index 577e7382921..d39cc3e321a 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -223,6 +223,21 @@ abstract class Configuration extends string { predicate hasFlowPath(SourcePathNode source, SinkPathNode sink) { flowsTo(source, _, sink, _, this) } + + /** + * Holds if the `pred` should be stored in the object `succ` under the property `prop`. + */ + predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * Holds if the property `prop` of the object `pred` should be loaded into `succ`. + */ + predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. + */ + predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } } /** @@ -449,6 +464,24 @@ abstract class AdditionalFlowStep extends DataFlow::Node { ) { none() } + + /** + * Holds if the `pred` should be stored in the object `succ` under the property `prop`. + */ + cached + predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * Holds if the property `prop` of the object `pred` should be loaded into `succ`. + */ + cached + predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + + /** + * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. + */ + cached + predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } } /** @@ -551,6 +584,13 @@ private predicate exploratoryFlowStep( basicFlowStep(pred, succ, _, cfg) or basicStoreStep(pred, succ, _) or basicLoadStep(pred, succ, _) or + + any(AdditionalFlowStep s).store(pred, succ, _) or + cfg.isAdditionalStoreStep(pred, succ, _) or + any(AdditionalFlowStep s).load(pred, succ, _) or + cfg.isAdditionalLoadStep(pred, succ, _) or + any(AdditionalFlowStep s).copyProperty(pred, succ, _) or + cfg.isAdditionalCopyPropertyStep(pred, succ, _) or // the following two disjuncts taken together over-approximate flow through // higher-order calls callback(pred, succ) or @@ -712,6 +752,12 @@ private predicate storeStep( basicStoreStep(pred, succ, prop) and summary = PathSummary::level() or + any(AdditionalFlowStep s).store(pred, succ, prop) and + summary = PathSummary::level() + or + cfg.isAdditionalStoreStep(pred, succ, prop) and + summary = PathSummary::level() + or exists(Function f, DataFlow::Node mid | // `f` stores its parameter `pred` in property `prop` of a value that flows back to the caller, // and `succ` is an invocation of `f` @@ -767,6 +813,12 @@ private predicate loadStep( basicLoadStep(pred, succ, prop) and summary = PathSummary::level() or + any(AdditionalFlowStep s).load(pred, succ, prop) and + summary = PathSummary::level() + or + cfg.isAdditionalLoadStep(pred, succ, prop) and + summary = PathSummary::level() + or exists(Function f, DataFlow::PropRead read | parameterPropRead(f, succ, pred, prop, read, cfg) and reachesReturn(f, read, cfg, summary) @@ -804,13 +856,31 @@ pragma[noinline] private predicate flowThroughProperty( DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg, PathSummary summary ) { - exists(string prop, DataFlow::Node base, PathSummary oldSummary, PathSummary newSummary | - reachableFromStoreBase(prop, pred, base, cfg, oldSummary) and - loadStep(base, succ, prop, cfg, newSummary) and + exists(string prop, DataFlow::Node storeBase, DataFlow::Node loadBase, PathSummary oldSummary, PathSummary newSummary | + reachableFromStoreBase(prop, pred, storeBase, cfg, oldSummary) and + (storeBase = loadBase or existsCopyProperty(storeBase, loadBase, prop)) and + loadStep(loadBase, succ, prop, cfg, newSummary) and summary = oldSummary.append(newSummary) ) } +/** + * Holds if the property `prop` is copied from `fromNode` to `toNode` using at least 1 step. + * + * The recursion of this predicate has been unfolded once compared to a naive implementation in order to avoid having no constraint on `prop`. + * Therefore a caller of this predicate should also test whether the `toNode` and `fromNode` are equal. + */ +private predicate existsCopyProperty(DataFlow::Node fromNode, DataFlow::Node toNode, string prop) { + exists(DataFlow::AdditionalFlowStep step, DataFlow::Node mid | + step.copyProperty(fromNode, mid, prop) and + ( + existsCopyProperty(mid, toNode, prop) + or + mid = toNode + ) + ) +} + /** * Holds if `arg` and `cb` are passed as arguments to a function which in turn * invokes `cb`, passing `arg` as its `i`th argument. diff --git a/javascript/ql/test/library-tests/Promises/Flowsteps.qll b/javascript/ql/test/library-tests/Promises/Flowsteps.qll deleted file mode 100644 index c18f87946ad..00000000000 --- a/javascript/ql/test/library-tests/Promises/Flowsteps.qll +++ /dev/null @@ -1,5 +0,0 @@ -import javascript - -query predicate flowSteps(DataFlow::Node pred, DataFlow::Node succ) { - any(DataFlow::AdditionalFlowStep step).step(pred, succ) -} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js new file mode 100644 index 00000000000..8b0b324d823 --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -0,0 +1,49 @@ +(async function () { + var source = "source"; + + var p1 = Promise.resolve(source); + sink(await p1); // NOT OK + + var p2 = new Promise((resolve, reject) => resolve(source)); + sink(await p2); // NOT OK + + var p3 = new Promise((resolve, reject) => reject(source)); + sink(await p3); // OK! + + var p4 = new Promise((resolve, reject) => reject(source)); + try { + var foo = await p4; + } catch(e) { + sink(e); // NOT OK! + } + + Promise.resolve(source).then(x => sink(x)); // NOT OK! + + Promise.resolve(source).then(x => foo(x), y => sink(y)); // OK! + + new Promise((resolve, reject) => reject(source)).then(x => sink(x)); // OK! + + new Promise((resolve, reject) => reject(source)).then(x => foo(x), y => sink(y)); // NOT OK! + + Promise.resolve("foo").then(x => source).then(z => sink(z)); // NOT OK! + + Promise.resolve(source).then(x => "foo").then(z => sink(z)); // OK! + + new Promise((resolve, reject) => reject(source)).catch(x => sink(x)); // NOT OK! + + Promise.resolve(source).catch(() => {}).then(a => sink(a)); // NOT OK! + + var p5 = Promise.resolve(source); + var p6 = p5.catch(() => {}); + var p7 = p6.then(a => sink(a)); // NOT OK! + + new Promise((resolve, reject) => reject(source)).then(() => {}).catch(x => sink(x)); // NOT OK! + + new Promise((resolve, reject) => reject(source)).then(() => {}, () => {}).catch(x => sink(x)); // OK! + + Promise.resolve(source).catch(() => {}).catch(() => {}).catch(() => {}).then(a => sink(a)); // NOT OK! + + Promise.resolve(source).finally(() => {}).then(a => sink(a)); // NOT OK! + + new Promise(() => {throw source}).catch(x => sink(x)); // NOT OK! +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/flow.qll b/javascript/ql/test/library-tests/Promises/flow.qll new file mode 100644 index 00000000000..60118aed6b5 --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/flow.qll @@ -0,0 +1,17 @@ +import javascript + +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "PromiseFlowTestingConfig" } + + override predicate isSource(DataFlow::Node source) { + source.getEnclosingExpr().getStringValue() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + any(DataFlow::InvokeNode call | call.getCalleeName() = "sink").getAnArgument() = sink + } +} + +query predicate flow(DataFlow::Node source, DataFlow::Node sink) { + any(Configuration a).hasFlow(source, sink) +} diff --git a/javascript/ql/test/library-tests/Promises/flowsteps.js b/javascript/ql/test/library-tests/Promises/flowsteps.js deleted file mode 100644 index 284008553c4..00000000000 --- a/javascript/ql/test/library-tests/Promises/flowsteps.js +++ /dev/null @@ -1,56 +0,0 @@ -(async function () { - function throws(resolve, reject) { - throw new Error() - } - new Promise(throws) - .catch((e) => console.log(e)); - - new Promise(throws) - .then((val) => console.log(val), (error) => console.log(error)); - - try { - await new Promise(throws); - } catch (e2) { - console.log(e2); - } - - new Promise((resolve, reject) => reject(3)) - .catch((e3) => console.log(e3)); - - try { - await new Promise((resolve, reject) => reject(4)); - } catch(e4) { - console.log(e4); - } - - new Promise(throws) - .then(() => {}) - .catch((e5) => console.log(e5)); - - - new Promise(throws) - .then(() => {}) - .catch((e6) => console.log(e6)) - .catch((e7) => console.log(e7)); - - var foo = await new Promise((resolve, reject) => resolve(8)) - - var bar = await new Promise((resolve, reject) => resolve(9)).then((x) => x + 2); - - var p = Promise.resolve(3); - var baz = await p.then((val) => val * 2); - - var p2 = new Promise((resolve, reject) => { - if (Math.random() > 0.5) { - resolve(13); - } else { - reject(14); - } - }); - var quz = await p2.then(val => val * 4).catch(e => e * 3); - - function returnsPromise() { - return Promise.resolve(3); - } - var a = await returnsPromise(); -})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/options b/javascript/ql/test/library-tests/Promises/options new file mode 100644 index 00000000000..ae107b46f9e --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/options @@ -0,0 +1 @@ +semmle-extractor-options: --experimental diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index a7351c85a13..80f6a918825 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -1,27 +1,34 @@ test_ResolvedPromiseDefinition -| flowsteps.js:40:11:40:28 | Promise.resolve(3) | flowsteps.js:40:27:40:27 | 3 | -| flowsteps.js:53:12:53:29 | Promise.resolve(3) | flowsteps.js:53:28:53:28 | 3 | +| flow.js:4:11:4:33 | Promise ... source) | flow.js:4:27:4:32 | source | +| flow.js:20:2:20:24 | Promise ... source) | flow.js:20:18:20:23 | source | +| flow.js:22:2:22:24 | Promise ... source) | flow.js:22:18:22:23 | source | +| flow.js:28:2:28:23 | Promise ... ("foo") | flow.js:28:18:28:22 | "foo" | +| flow.js:30:2:30:24 | Promise ... source) | flow.js:30:18:30:23 | source | +| flow.js:34:2:34:24 | Promise ... source) | flow.js:34:18:34:23 | source | +| flow.js:36:11:36:33 | Promise ... source) | flow.js:36:27:36:32 | source | +| flow.js:44:2:44:24 | Promise ... source) | flow.js:44:18:44:23 | source | +| flow.js:46:2:46:24 | Promise ... source) | flow.js:46:18:46:23 | source | | promises.js:53:19:53:41 | Promise ... source) | promises.js:53:35:53:40 | source | | promises.js:62:19:62:41 | Promise ... source) | promises.js:62:35:62:40 | source | | promises.js:71:5:71:27 | Promise ... source) | promises.js:71:21:71:26 | source | test_PromiseDefinition_getARejectHandler -| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:6:12:6:32 | (e) => ... .log(e) | -| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:9:38:9:66 | (error) ... (error) | -| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:18:12:18:34 | (e3) => ... log(e3) | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:69:26:80 | y => sink(y) | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:66:42:73 | () => {} | +| flow.js:48:2:48:34 | new Pro ... ource}) | flow.js:48:42:48:53 | x => sink(x) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:20:6:22:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | test_PromiseDefinition_getExecutor -| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | -| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | -| flowsteps.js:12:11:12:29 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | -| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:17:15:17:44 | (resolv ... ject(3) | -| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | flowsteps.js:21:23:21:52 | (resolv ... ject(4) | -| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | -| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:2:3:4:3 | functio ... r()\\n } | -| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | flowsteps.js:36:31:36:61 | (resolv ... olve(8) | -| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:31:38:61 | (resolv ... olve(9) | -| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:43:24:49:3 | (resolv ... }\\n } | +| flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:23:7:58 | (resolv ... source) | +| flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:23:10:57 | (resolv ... source) | +| flow.js:13:11:13:58 | new Pro ... ource)) | flow.js:13:23:13:57 | (resolv ... source) | +| flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:14:24:48 | (resolv ... source) | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:14:26:48 | (resolv ... source) | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:14:32:48 | (resolv ... source) | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:14:40:48 | (resolv ... source) | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:14:42:48 | (resolv ... source) | +| flow.js:48:2:48:34 | new Pro ... ource}) | flow.js:48:14:48:33 | () => {throw source} | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:31:35:5 | functio ... ;\\n } | @@ -29,103 +36,70 @@ test_PromiseDefinition_getExecutor test_PromiseDefinition_getAFinallyHandler | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | test_PromiseDefinition -| flowsteps.js:5:3:5:21 | new Promise(throws) | -| flowsteps.js:8:3:8:21 | new Promise(throws) | -| flowsteps.js:12:11:12:29 | new Promise(throws) | -| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | -| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | -| flowsteps.js:26:3:26:21 | new Promise(throws) | -| flowsteps.js:31:3:31:21 | new Promise(throws) | -| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | -| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | -| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | +| flow.js:7:11:7:59 | new Pro ... ource)) | +| flow.js:10:11:10:58 | new Pro ... ource)) | +| flow.js:13:11:13:58 | new Pro ... ource)) | +| flow.js:24:2:24:49 | new Pro ... ource)) | +| flow.js:26:2:26:49 | new Pro ... ource)) | +| flow.js:32:2:32:49 | new Pro ... ource)) | +| flow.js:40:2:40:49 | new Pro ... ource)) | +| flow.js:42:2:42:49 | new Pro ... ource)) | +| flow.js:48:2:48:34 | new Pro ... ource}) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | | promises.js:33:19:35:6 | new Pro ... \\n }) | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | test_PromiseDefinition_getAResolveHandler -| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:9:11:9:35 | (val) = ... og(val) | -| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:27:11:27:18 | () => {} | -| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:32:11:32:18 | () => {} | -| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:69:38:80 | (x) => x + 2 | -| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:50:27:50:40 | val => val * 4 | +| flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:56:24:67 | x => sink(x) | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:56:26:66 | x => foo(x) | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:56:40:63 | () => {} | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:56:42:63 | () => {} | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:36:18:38:5 | functio ... ;\\n } | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:46:18:48:5 | functio ... ;\\n } | test_PromiseDefinition_getRejectParameter -| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | -| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | -| flowsteps.js:12:11:12:29 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | -| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:17:25:17:30 | reject | -| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | flowsteps.js:21:33:21:38 | reject | -| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | -| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:2:28:2:33 | reject | -| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | flowsteps.js:36:41:36:46 | reject | -| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:41:38:46 | reject | -| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:43:34:43:39 | reject | +| flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:33:7:38 | reject | +| flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:33:10:38 | reject | +| flow.js:13:11:13:58 | new Pro ... ource)) | flow.js:13:33:13:38 | reject | +| flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:24:24:29 | reject | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:24:26:29 | reject | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:24:32:29 | reject | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:24:40:29 | reject | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:24:42:29 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:50:33:55 | reject | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:48:43:53 | reject | test_PromiseDefinition_getResolveParameter -| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | -| flowsteps.js:8:3:8:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | -| flowsteps.js:12:11:12:29 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | -| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:17:16:17:22 | resolve | -| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | flowsteps.js:21:24:21:30 | resolve | -| flowsteps.js:26:3:26:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | -| flowsteps.js:31:3:31:21 | new Promise(throws) | flowsteps.js:2:19:2:25 | resolve | -| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | flowsteps.js:36:32:36:38 | resolve | -| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | flowsteps.js:38:32:38:38 | resolve | -| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | flowsteps.js:43:25:43:31 | resolve | +| flow.js:7:11:7:59 | new Pro ... ource)) | flow.js:7:24:7:30 | resolve | +| flow.js:10:11:10:58 | new Pro ... ource)) | flow.js:10:24:10:30 | resolve | +| flow.js:13:11:13:58 | new Pro ... ource)) | flow.js:13:24:13:30 | resolve | +| flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:15:24:21 | resolve | +| flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:15:26:21 | resolve | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:15:32:21 | resolve | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:15:40:21 | resolve | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:15:42:21 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:41:33:47 | resolve | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:39:43:45 | resolve | test_PromiseDefinition_getACatchHandler -| flowsteps.js:5:3:5:21 | new Promise(throws) | flowsteps.js:6:12:6:32 | (e) => ... .log(e) | -| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | flowsteps.js:18:12:18:34 | (e3) => ... log(e3) | +| flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | +| flow.js:48:2:48:34 | new Pro ... ource}) | flow.js:48:42:48:53 | x => sink(x) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | -flowSteps -| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:6:13:6:13 | e | -| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:9:39:9:43 | error | -| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:13:12:13:13 | e2 | -| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:28:13:28:14 | e5 | -| flowsteps.js:2:3:4:3 | exceptional return of function throws | flowsteps.js:33:13:33:14 | e6 | -| flowsteps.js:17:15:17:44 | exceptional return of anonymous function | flowsteps.js:18:13:18:14 | e3 | -| flowsteps.js:17:43:17:43 | 3 | flowsteps.js:18:13:18:14 | e3 | -| flowsteps.js:21:23:21:52 | exceptional return of anonymous function | flowsteps.js:22:11:22:12 | e4 | -| flowsteps.js:21:51:21:51 | 4 | flowsteps.js:22:11:22:12 | e4 | -| flowsteps.js:27:11:27:18 | exceptional return of anonymous function | flowsteps.js:28:13:28:14 | e5 | -| flowsteps.js:32:11:32:18 | exceptional return of anonymous function | flowsteps.js:33:13:33:14 | e6 | -| flowsteps.js:33:12:33:34 | exceptional return of anonymous function | flowsteps.js:34:13:34:14 | e7 | -| flowsteps.js:36:31:36:61 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | -| flowsteps.js:36:60:36:60 | 8 | flowsteps.js:36:13:36:62 | await n ... lve(8)) | -| flowsteps.js:38:31:38:61 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | -| flowsteps.js:38:60:38:60 | 9 | flowsteps.js:38:70:38:70 | x | -| flowsteps.js:38:69:38:80 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | -| flowsteps.js:38:76:38:80 | x + 2 | flowsteps.js:38:13:38:81 | await n ... x + 2) | -| flowsteps.js:40:27:40:27 | 3 | flowsteps.js:41:27:41:29 | val | -| flowsteps.js:41:26:41:41 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | -| flowsteps.js:41:35:41:41 | val * 2 | flowsteps.js:41:13:41:42 | await p ... al * 2) | -| flowsteps.js:43:24:49:3 | exceptional return of anonymous function | flowsteps.js:50:49:50:49 | e | -| flowsteps.js:45:12:45:13 | 13 | flowsteps.js:50:27:50:29 | val | -| flowsteps.js:47:11:47:12 | 14 | flowsteps.js:50:49:50:49 | e | -| flowsteps.js:50:27:50:40 | exceptional return of anonymous function | flowsteps.js:50:49:50:49 | e | -| flowsteps.js:50:34:50:40 | val * 4 | flowsteps.js:50:13:50:59 | await p ... e * 3) | -| flowsteps.js:50:49:50:58 | exceptional return of anonymous function | flowsteps.js:1:2:56:1 | exceptional return of anonymous function | -| flowsteps.js:50:54:50:58 | e * 3 | flowsteps.js:50:13:50:59 | await p ... e * 3) | -| flowsteps.js:53:28:53:28 | 3 | flowsteps.js:55:11:55:32 | await r ... omise() | -| promises.js:4:13:4:18 | source | promises.js:6:26:6:28 | val | -| promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:20:7:20:7 | v | -| promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:23:19:23:19 | v | -| promises.js:14:11:14:20 | res_source | promises.js:18:18:18:18 | v | -| promises.js:16:11:16:20 | rej_source | promises.js:20:7:20:7 | v | -| promises.js:16:11:16:20 | rej_source | promises.js:23:19:23:19 | v | -| promises.js:34:17:34:22 | source | promises.js:36:28:36:30 | val | -| promises.js:44:17:44:22 | source | promises.js:46:28:46:30 | val | -| promises.js:53:35:53:40 | source | promises.js:54:28:54:30 | val | -| promises.js:62:35:62:40 | source | promises.js:63:28:63:30 | val | -| promises.js:71:21:71:26 | source | promises.js:71:34:71:36 | val | +flow +| flow.js:2:15:2:22 | "source" | flow.js:5:7:5:14 | await p1 | +| flow.js:2:15:2:22 | "source" | flow.js:8:7:8:14 | await p2 | +| flow.js:2:15:2:22 | "source" | flow.js:17:8:17:8 | e | +| flow.js:2:15:2:22 | "source" | flow.js:20:41:20:41 | x | +| flow.js:2:15:2:22 | "source" | flow.js:26:79:26:79 | y | +| flow.js:2:15:2:22 | "source" | flow.js:28:58:28:58 | z | +| flow.js:2:15:2:22 | "source" | flow.js:32:67:32:67 | x | +| flow.js:2:15:2:22 | "source" | flow.js:34:57:34:57 | a | +| flow.js:2:15:2:22 | "source" | flow.js:38:29:38:29 | a | +| flow.js:2:15:2:22 | "source" | flow.js:40:82:40:82 | x | +| flow.js:2:15:2:22 | "source" | flow.js:44:89:44:89 | a | +| flow.js:2:15:2:22 | "source" | flow.js:46:59:46:59 | a | +| flow.js:2:15:2:22 | "source" | flow.js:48:52:48:52 | x | diff --git a/javascript/ql/test/library-tests/Promises/tests.ql b/javascript/ql/test/library-tests/Promises/tests.ql index b2ff77cd0a1..2490f378970 100644 --- a/javascript/ql/test/library-tests/Promises/tests.ql +++ b/javascript/ql/test/library-tests/Promises/tests.ql @@ -7,4 +7,4 @@ import PromiseDefinition_getAResolveHandler import PromiseDefinition_getRejectParameter import PromiseDefinition_getResolveParameter import PromiseDefinition_getACatchHandler -import Flowsteps \ No newline at end of file +import flow \ No newline at end of file From b7325232d7e367dce31013653e6264b6fb16ae3b Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Tue, 14 Jan 2020 23:07:21 +0100 Subject: [PATCH 009/148] Query to detect LDAP injections in Java Consider DNs as injection points as well Add more taint steps --- .../Security/CWE/CWE-90/LdapInjectionLib.qll | 357 +++++++++++++++--- 1 file changed, 312 insertions(+), 45 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll index 84dba816a27..4a78d8282bc 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll @@ -12,9 +12,9 @@ class TypeLdapContext extends Interface { TypeLdapContext() { this.hasQualifiedName("javax.naming.ldap", "LdapContext") } } -/** The class `com.unboundid.ldap.sdk.SearchRequest`. */ -class TypeUnboundIdSearchRequest extends Class { - TypeUnboundIdSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") } +/** The class `javax.naming.ldap.LdapName`. */ +class TypeLdapName extends Class { + TypeLdapName() { this.hasQualifiedName("javax.naming.ldap", "LdapName") } } /** The interface `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */ @@ -24,6 +24,11 @@ class TypeReadOnlySearchRequest extends Interface { } } +/** The class `com.unboundid.ldap.sdk.SearchRequest`. */ +class TypeUnboundIdSearchRequest extends Class { + TypeUnboundIdSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") } +} + /** The class `com.unboundid.ldap.sdk.Filter`. */ class TypeUnboundIdLdapFilter extends Class { TypeUnboundIdLdapFilter() { this.hasQualifiedName("com.unboundid.ldap.sdk", "Filter") } @@ -44,14 +49,28 @@ class TypeLdapQuery extends Interface { TypeLdapQuery() { this.hasQualifiedName("org.springframework.ldap.query", "LdapQuery") } } -/** The interface `org.springframework.ldap.query.LdapQueryBuilder`. */ +/** The class `org.springframework.ldap.query.LdapQueryBuilder`. */ class TypeLdapQueryBuilder extends Class { TypeLdapQueryBuilder() { this.hasQualifiedName("org.springframework.ldap.query", "LdapQueryBuilder") } } -/** The interface `org.springframework.ldap.filter.HardcodedFilter`. */ +/** The interface `org.springframework.ldap.query.ConditionCriteria`. */ +class TypeConditionCriteria extends Interface { + TypeConditionCriteria() { + this.hasQualifiedName("org.springframework.ldap.query", "ConditionCriteria") + } +} + +/** The interface `org.springframework.ldap.query.ContainerCriteria`. */ +class TypeContainerCriteria extends Interface { + TypeContainerCriteria() { + this.hasQualifiedName("org.springframework.ldap.query", "ContainerCriteria") + } +} + +/** The class `org.springframework.ldap.filter.HardcodedFilter`. */ class TypeHardcodedFilter extends Class { TypeHardcodedFilter() { this.hasQualifiedName("org.springframework.ldap.filter", "HardcodedFilter") @@ -63,6 +82,18 @@ class TypeSpringLdapFilter extends Interface { TypeSpringLdapFilter() { this.hasQualifiedName("org.springframework.ldap.filter", "Filter") } } +/** The class `org.springframework.ldap.support.LdapNameBuilder`. */ +class TypeLdapNameBuilder extends Class { + TypeLdapNameBuilder() { + this.hasQualifiedName("org.springframework.ldap.support", "LdapNameBuilder") + } +} + +/** The class `org.springframework.ldap.support.LdapUtils`. */ +class TypeLdapUtils extends Class { + TypeLdapUtils() { this.hasQualifiedName("org.springframework.ldap.support", "LdapUtils") } +} + /** The interface `org.apache.directory.ldap.client.api.LdapConnection`. */ class TypeLdapConnection extends Interface { TypeLdapConnection() { @@ -77,6 +108,13 @@ class TypeApacheSearchRequest extends Interface { } } +/** The class `org.apache.directory.api.ldap.model.name.Dn`. */ +class TypeApacheDn extends Class { + TypeApacheDn() { + this.hasQualifiedName("org.apache.directory.api.ldap.model.name", "Dn") + } +} + /** The class `org.springframework.ldap.support.LdapEncoder`. */ class TypeLdapEncoder extends Class { TypeLdapEncoder() { this.hasQualifiedName("org.springframework.ldap.support", "LdapEncoder") } @@ -110,11 +148,26 @@ class LdapInjectionFlowConfig extends TaintTracking::Configuration { override predicate isSanitizer(DataFlow::Node node) { node instanceof LdapInjectionSanitizer } override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { + ldapNameStep(node1, node2) or + ldapNameAddAllStep(node1, node2) or + ldapNameGetCloneStep(node1, node2) or filterStep(node1, node2) or + filterToStringStep(node1, node2) or unboundIdSearchRequestStep(node1, node2) or + unboundIdSearchRequestDuplicateStep(node1, node2) or + unboundIdSearchRequestSetStep(node1, node2) or ldapQueryStep(node1, node2) or + ldapQueryBaseStep(node1, node2) or + ldapQueryBuilderStep(node1, node2) or hardcodedFilterStep(node1, node2) or - apacheSearchRequestStep(node1, node2) + springLdapFilterToStringStep(node1, node2) or + ldapNameBuilderStep(node1, node2) or + ldapNameBuilderBuildStep(node1, node2) or + ldapUtilsStep(node1, node2) or + apacheSearchRequestStep(node1, node2) or + apacheSearchRequestGetStep(node1, node2) or + apacheLdapDnStep(node1, node2) or + apacheLdapDnGetStep(node1, node2) } } @@ -129,8 +182,8 @@ class LocalSource extends LdapInjectionSource { } /** - * JNDI sink for LDAP injection vulnerabilities, i.e. 2nd argument to search method from - * DirContext, InitialDirContext, LdapContext or InitialLdapContext. + * JNDI sink for LDAP injection vulnerabilities, i.e. 1st (DN) or 2nd (filter) argument to + * `search` method from `DirContext` or `LdapContext`. */ class JndiLdapInjectionSink extends LdapInjectionSink { JndiLdapInjectionSink() { @@ -143,14 +196,14 @@ class JndiLdapInjectionSink extends LdapInjectionSink { m.getDeclaringType().getAnAncestor() instanceof TypeLdapContext ) and m.hasName("search") and - index = 1 + index in [0..1] ) } } /** * UnboundID sink for LDAP injection vulnerabilities, - * i.e. LDAPConnection.search or LDAPConnection.searchForEntry method. + * i.e. LDAPConnection.search, LDAPConnection.asyncSearch or LDAPConnection.searchForEntry method. */ class UnboundIdLdapInjectionSink extends LdapInjectionSink { UnboundIdLdapInjectionSink() { @@ -159,18 +212,11 @@ class UnboundIdLdapInjectionSink extends LdapInjectionSink { ma.getArgument(index) = this.getExpr() and m.getParameter(index) = param | - // LDAPConnection.search or LDAPConnection.searchForEntry method + // LDAPConnection.search, LDAPConnection.asyncSearch or LDAPConnection.searchForEntry method m.getDeclaringType() instanceof TypeLDAPConnection and - (m.hasName("search") or m.hasName("searchForEntry")) and + (m.hasName("search") or m.hasName("asyncSearch") or m.hasName("searchForEntry")) and // Parameter is not varargs - not isVarargs(m, index) and - ( - // Parameter type is SearchRequest or ReadOnlySearchRequest - param.getType() instanceof TypeReadOnlySearchRequest or - param.getType() instanceof TypeUnboundIdSearchRequest or - // Or parameter index is 2, 3, 5, 6 or 7 (this is where filter parameter is) - index = any(int i | i = [2..3] or i = [5..7]) - ) + not isVarargs(m, index) ) } } @@ -197,11 +243,10 @@ class SpringLdapInjectionSink extends LdapInjectionSink { m.hasName("searchForObject") ) and ( - // Parameter type is LdapQuery or Filter - paramType instanceof TypeLdapQuery or - paramType instanceof TypeSpringLdapFilter or - // Or parameter index is 1 (this is where filter parameter is) - index = 1 + // Parameter index is 1 (DN or query) or 2 (filter) if method is not authenticate + (index in [0..1] and not m.hasName("authenticate")) or + // But it's not the last parameter in case of authenticate method (last param is password) + (index in [0..1] and index < m.getNumberOfParameters() - 1 and m.hasName("authenticate")) ) ) } @@ -215,15 +260,9 @@ class ApacheLdapInjectionSink extends LdapInjectionSink { ma.getArgument(index) = this.getExpr() and m.getParameterType(index) = paramType | - // LdapConnection.search method m.getDeclaringType().getAnAncestor() instanceof TypeLdapConnection and m.hasName("search") and - ( - // Parameter type is SearchRequest - paramType instanceof TypeApacheSearchRequest or - // Or parameter index is 1 (this is where filter parameter is) - index = 1 - ) + not isVarargs(m, index) ) } } @@ -261,9 +300,51 @@ class UnboundIdSanitizer extends LdapInjectionSanitizer { } } +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and `LdapName`, + * i.e. `new LdapName(tainted)`. + */ +predicate ldapNameStep(ExprNode n1, ExprNode n2) { + exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeLdapName | + n1.asExpr() = cc.getAnArgument() and + n2.asExpr() = cc + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `List` and `LdapName`, + * i.e. `new LdapName().addAll(tainted)`. + */ +predicate ldapNameAddAllStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getAnArgument() and + (n2.asExpr() = ma or n2.asExpr() = ma.getQualifier()) + | + ma.getMethod() = m and + m.getDeclaringType() instanceof TypeLdapName and + m.hasName("addAll") + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `LdapName` and `LdapName` or + * `String`, i.e. `taintedLdapName.clone()`, `taintedLdapName.getAll()`, + * `taintedLdapName.getRdns()` or `taintedLdapName.toString()`. + */ +predicate ldapNameGetCloneStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + n2.asExpr() = ma + | + ma.getMethod() = m and + m.getDeclaringType() instanceof TypeLdapName and + (m.hasName("clone") or m.hasName("getAll") or m.hasName("getRdns") or m.hasName("toString")) + ) +} + /** * Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID `Filter`, - * i.e. `Filter.create(tainted)`. + * i.e. `Filter.create*(tainted)`. */ predicate filterStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m | @@ -272,14 +353,34 @@ predicate filterStep(ExprNode n1, ExprNode n2) { n2.asExpr() = ma and ma.getMethod() = m and m.getDeclaringType() instanceof TypeUnboundIdLdapFilter and - m.hasName("create") + ( + m.hasName("create") or + m.hasName("createANDFilter") or + m.hasName("createNOTFilter") or + m.hasName("createORFilter") or + m.hasName("simplifyFilter") + ) + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between UnboundID `Filter` and `String`, + * i.e. `taintedFilter.toString()` or `taintedFilter.toString(buffer)`. + */ +predicate filterToStringStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + (n2.asExpr() = ma or n2.asExpr() = ma.getAnArgument()) + | + ma.getMethod() = m and + m.getDeclaringType() instanceof TypeUnboundIdLdapFilter and + (m.hasName("toString") or m.hasName("toNormalizedString")) ) } /** * Holds if `n1` to `n2` is a dataflow step that converts between `String` and UnboundID - * `SearchRequest`, i.e. `new SearchRequest([...], tainted, [...])`, where `tainted` is - * parameter number 3, 4, 7, 8 or 9, but is not varargs. + * `SearchRequest`, i.e. `new SearchRequest(tainted)`. */ predicate unboundIdSearchRequestStep(ExprNode n1, ExprNode n2) { exists(ConstructorCall cc, Constructor c, int index | @@ -288,15 +389,43 @@ predicate unboundIdSearchRequestStep(ExprNode n1, ExprNode n2) { n1.asExpr() = cc.getArgument(index) and n2.asExpr() = cc and c = cc.getConstructor() and - // not c.getParameter(min(int i | i = index or i = c.getNumberOfParameters() - 1 | i)).isVarargs() and - not isVarargs(c, index) and - index = any(int i |i = [2..3] or i = [6..8]) + not isVarargs(c, index) + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between UnboundID `SearchRequest` + * and UnboundID `SearchRequest`, i.e. `taintedSearchRequest.duplicate()`. + */ +predicate unboundIdSearchRequestDuplicateStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + n2.asExpr() = ma + | + ma.getMethod() = m and + m.getDeclaringType().getAnAncestor() instanceof TypeReadOnlySearchRequest and + m.hasName("duplicate") + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between DN or filter and UnboundID + * `SearchRequest`, i.e. `searchRequest.setBaseDN(tainted)` or `searchRequest.setFilter(tainted)`. + */ +predicate unboundIdSearchRequestSetStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getAnArgument() and + n2.asExpr() = ma.getQualifier() + | + ma.getMethod() = m and + m.getDeclaringType() instanceof TypeUnboundIdSearchRequest and + (m.hasName("setBaseDN") or m.hasName("setFilter")) ) } /** * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring `LdapQuery`, - * i.e. `LdapQueryBuilder.query().filter(tainted)`. + * i.e. `LdapQueryBuilder.query().filter(tainted)` or `LdapQueryBuilder.query().base(tainted)`. */ predicate ldapQueryStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m, int index | @@ -305,11 +434,51 @@ predicate ldapQueryStep(ExprNode n1, ExprNode n2) { n2.asExpr() = ma and ma.getMethod() = m and m.getDeclaringType() instanceof TypeLdapQueryBuilder and - m.hasName("filter") and + (m.hasName("filter") or m.hasName("base")) and index = 0 ) } +/** + * Holds if `n1` to `n2` is a dataflow step that converts between Spring `LdapQueryBuilder` and + * `Name`, i.e. `taintedLdapQueryBuilder.base()`. + */ +predicate ldapQueryBaseStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + n2.asExpr() = ma + | + ma.getMethod() = m and + m.getDeclaringType() instanceof TypeLdapQueryBuilder and + m.hasName("base") and + m.getNumberOfParameters() = 0 + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between Spring `LdapQueryBuilder`, + * `ConditionCriteria` or `ContainerCriteria`, i.e. when the query is built, for example + * `query().base(tainted).where("objectclass").is("person")`. + */ +predicate ldapQueryBuilderStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + n2.asExpr() = ma + | + ma.getMethod() = m and + ( + m.getDeclaringType() instanceof TypeLdapQueryBuilder or + m.getDeclaringType() instanceof TypeConditionCriteria or + m.getDeclaringType() instanceof TypeContainerCriteria + ) and + ( + m.getReturnType() instanceof TypeLdapQueryBuilder or + m.getReturnType() instanceof TypeConditionCriteria or + m.getReturnType() instanceof TypeContainerCriteria + ) + ) +} + /** * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring * `HardcodedFilter`, i.e. `new HardcodedFilter(tainted)`. @@ -321,17 +490,115 @@ predicate hardcodedFilterStep(ExprNode n1, ExprNode n2) { ) } +/** + * Holds if `n1` to `n2` is a dataflow step that converts between Spring `Filter` and + * `String`, i.e. `taintedFilter.toString()`, `taintedFilter.encode()` or + * `taintedFilter.encode(buffer)`. + */ +predicate springLdapFilterToStringStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + (n2.asExpr() = ma or n2.asExpr() = ma.getAnArgument()) + | + ma.getMethod() = m and + m.getDeclaringType().getAnAncestor() instanceof TypeSpringLdapFilter and + (m.hasName("encode") or m.hasName("toString")) + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Spring + * `LdapNameBuilder`, i.e. `LdapNameBuilder.newInstance(tainted)` or + * `LdapNameBuilder.newInstance().add(tainted)`. + */ +predicate ldapNameBuilderStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | n1.asExpr() = ma.getAnArgument() | + (n2.asExpr() = ma or n2.asExpr() = ma.getQualifier()) and + ma.getMethod() = m and + m.getDeclaringType() instanceof TypeLdapNameBuilder and + (m.hasName("newInstance") or m.hasName("add")) and + m.getNumberOfParameters() = 1 + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between tainted Spring `LdapNameBuilder` + * and `LdapName`, `LdapNameBuilder.build()`. + */ +predicate ldapNameBuilderBuildStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() | + n2.asExpr() = ma and + ma.getMethod() = m and + m.getDeclaringType() instanceof TypeLdapNameBuilder and + m.hasName("build") + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and `LdapName` via + * Spring `LdapUtils.newLdapName`, i.e. `LdapUtils.newLdapName(tainted)`. + */ +predicate ldapUtilsStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | n1.asExpr() = ma.getAnArgument() | + n2.asExpr() = ma and + ma.getMethod() = m and + m.getDeclaringType() instanceof TypeLdapUtils and + m.hasName("newLdapName") + ) +} + /** * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Apache LDAP API - * `SearchRequest`, i.e. `SearchRequest s = new SearchRequestImpl(); s.setFilter(tainted");`. + * `SearchRequest`, i.e. `searchRequest.setFilter(tainted)` or `searchRequest.setBase(tainted)`. */ predicate apacheSearchRequestStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m | - n1.asExpr() = ma.getAnArgument() + n1.asExpr() = ma.getAnArgument() and + n2.asExpr() = ma.getQualifier() | - n2.asExpr() = ma.getQualifier() and ma.getMethod() = m and m.getDeclaringType().getAnAncestor() instanceof TypeApacheSearchRequest and - m.hasName("setFilter") + (m.hasName("setFilter") or m.hasName("setBase")) + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between Apache LDAP API `SearchRequest` + * and filter or DN i.e. `tainterSearchRequest.getFilter()` or `taintedSearchRequest.getBase()`. + */ +predicate apacheSearchRequestGetStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + n2.asExpr() = ma + | + ma.getMethod() = m and + m.getDeclaringType().getAnAncestor() instanceof TypeApacheSearchRequest and + (m.hasName("getFilter") or m.hasName("getBase")) + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between `String` and Apache LDAP API + * `Dn`, i.e. `new Dn(tainted)`. + */ +predicate apacheLdapDnStep(ExprNode n1, ExprNode n2) { + exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeApacheDn | + n1.asExpr() = cc.getAnArgument() and + n2.asExpr() = cc + ) +} + +/** + * Holds if `n1` to `n2` is a dataflow step that converts between Apache LDAP API `Dn` + * and `String` i.e. `taintedDn.getName()`, `taintedDn.getNormName()` or `taintedDn.toString()`. + */ +predicate apacheLdapDnGetStep(ExprNode n1, ExprNode n2) { + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getQualifier() and + n2.asExpr() = ma + | + ma.getMethod() = m and + m.getDeclaringType().getAnAncestor() instanceof TypeApacheDn and + (m.hasName("getName") or m.hasName("getNormName") or m.hasName("toString")) ) } \ No newline at end of file From 830100d2ed7a956ce9b67f8d48a7b43520bdfa72 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 15 Jan 2020 13:48:16 +0100 Subject: [PATCH 010/148] support interprocedural flow with custom load/store steps --- .../javascript/dataflow/Configuration.qll | 44 +++++++++++++------ .../CustomLoadStoreSteps/test.expected | 1 + .../CustomLoadStoreSteps/test.ql | 22 ++++++++++ .../library-tests/CustomLoadStoreSteps/tst.js | 10 +++++ .../ql/test/library-tests/Promises/flow.js | 21 +++++---- .../test/library-tests/Promises/interflow.js | 20 +++++++++ .../library-tests/Promises/tests.expected | 31 ++++++++----- 7 files changed, 115 insertions(+), 34 deletions(-) create mode 100644 javascript/ql/test/library-tests/CustomLoadStoreSteps/test.expected create mode 100644 javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql create mode 100644 javascript/ql/test/library-tests/CustomLoadStoreSteps/tst.js create mode 100644 javascript/ql/test/library-tests/Promises/interflow.js diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index d39cc3e321a..58f31aae0f7 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -466,22 +466,22 @@ abstract class AdditionalFlowStep extends DataFlow::Node { } /** - * Holds if the `pred` should be stored in the object `succ` under the property `prop`. + * Holds if the `pred` should be stored in the object `succ` under the property `prop`. */ cached predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } /** - * Holds if the property `prop` of the object `pred` should be loaded into `succ`. + * Holds if the property `prop` of the object `pred` should be loaded into `succ`. */ cached predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } /** - * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. + * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. */ cached - predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } } /** @@ -584,7 +584,7 @@ private predicate exploratoryFlowStep( basicFlowStep(pred, succ, _, cfg) or basicStoreStep(pred, succ, _) or basicLoadStep(pred, succ, _) or - + any(AdditionalFlowStep s).store(pred, succ, _) or cfg.isAdditionalStoreStep(pred, succ, _) or any(AdditionalFlowStep s).load(pred, succ, _) or @@ -765,6 +765,16 @@ private predicate storeStep( ( returnedPropWrite(f, _, prop, mid) or + exists(DataFlow::SourceNode base | + ( + any(AdditionalFlowStep step).store(mid, _, prop) + or + cfg.isAdditionalStoreStep(mid, _, prop) + ) + and + base.flowsToExpr(f.getAReturnedExpr()) + ) + or succ instanceof DataFlow::NewNode and receiverPropWrite(f, prop, mid) ) @@ -775,12 +785,18 @@ private predicate storeStep( * Holds if `f` may `read` property `prop` of parameter `parm`. */ private predicate parameterPropRead( - Function f, DataFlow::Node invk, DataFlow::Node arg, string prop, DataFlow::PropRead read, + Function f, DataFlow::Node invk, DataFlow::Node arg, string prop, DataFlow::Node read, DataFlow::Configuration cfg ) { - exists(DataFlow::SourceNode parm | + exists(DataFlow::Node parm | callInputStep(f, invk, arg, parm, cfg) and - read = parm.getAPropertyRead(prop) + ( + read = parm.(DataFlow::SourceNode).getAPropertyRead(prop) + or + any(AdditionalFlowStep step).load(parm, read, prop) + or + cfg.isAdditionalLoadStep(parm, read, prop) + ) ) } @@ -819,7 +835,7 @@ private predicate loadStep( cfg.isAdditionalLoadStep(pred, succ, prop) and summary = PathSummary::level() or - exists(Function f, DataFlow::PropRead read | + exists(Function f, DataFlow::Node read | parameterPropRead(f, succ, pred, prop, read, cfg) and reachesReturn(f, read, cfg, summary) ) @@ -866,17 +882,17 @@ private predicate flowThroughProperty( /** * Holds if the property `prop` is copied from `fromNode` to `toNode` using at least 1 step. - * - * The recursion of this predicate has been unfolded once compared to a naive implementation in order to avoid having no constraint on `prop`. - * Therefore a caller of this predicate should also test whether the `toNode` and `fromNode` are equal. + * + * The recursion of this predicate has been unfolded once compared to a naive implementation in order to avoid having no constraint on `prop`. + * Therefore a caller of this predicate should also test whether the `toNode` and `fromNode` are equal. */ private predicate existsCopyProperty(DataFlow::Node fromNode, DataFlow::Node toNode, string prop) { - exists(DataFlow::AdditionalFlowStep step, DataFlow::Node mid | + exists(DataFlow::AdditionalFlowStep step, DataFlow::Node mid | step.copyProperty(fromNode, mid, prop) and ( existsCopyProperty(mid, toNode, prop) or - mid = toNode + mid = toNode ) ) } diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.expected b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.expected new file mode 100644 index 00000000000..b53738c569a --- /dev/null +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.expected @@ -0,0 +1 @@ +| tst.js:4:15:4:22 | "source" | tst.js:9:7:9:24 | readTaint(tainted) | diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql new file mode 100644 index 00000000000..7b27d81e18a --- /dev/null +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql @@ -0,0 +1,22 @@ +import javascript + +class Configuration extends TaintTracking::Configuration { + Configuration() { this = "PromiseFlowTestingConfig" } + + override predicate isSource(DataFlow::Node source) { + source.getEnclosingExpr().getStringValue() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + any(DataFlow::InvokeNode call | call.getCalleeName() = "sink").getAnArgument() = sink + } + + // When the source code states that "foo" is being read, "bar" is additionally being read. + override predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + pred.(DataFlow::SourceNode).getAPropertyRead("foo") = succ and prop = "bar" + } +} + +from DataFlow::Node pred, DataFlow::Node succ, Configuration cfg +where cfg.hasFlow(pred, succ) +select pred, succ diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/tst.js b/javascript/ql/test/library-tests/CustomLoadStoreSteps/tst.js new file mode 100644 index 00000000000..c3fff1b4187 --- /dev/null +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/tst.js @@ -0,0 +1,10 @@ +// When the source code states that "foo" is being read, "bar" is additionally being read. + +(function () { + var source = "source"; + var tainted = { bar: source }; + function readTaint(x) { + return x.foo; + } + sink(readTaint(tainted)); +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js index 8b0b324d823..3d6bd556bdd 100644 --- a/javascript/ql/test/library-tests/Promises/flow.js +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -13,7 +13,7 @@ var p4 = new Promise((resolve, reject) => reject(source)); try { var foo = await p4; - } catch(e) { + } catch (e) { sink(e); // NOT OK! } @@ -31,19 +31,24 @@ new Promise((resolve, reject) => reject(source)).catch(x => sink(x)); // NOT OK! - Promise.resolve(source).catch(() => {}).then(a => sink(a)); // NOT OK! + Promise.resolve(source).catch(() => { }).then(a => sink(a)); // NOT OK! var p5 = Promise.resolve(source); - var p6 = p5.catch(() => {}); + var p6 = p5.catch(() => { }); var p7 = p6.then(a => sink(a)); // NOT OK! - new Promise((resolve, reject) => reject(source)).then(() => {}).catch(x => sink(x)); // NOT OK! + new Promise((resolve, reject) => reject(source)).then(() => { }).catch(x => sink(x)); // NOT OK! - new Promise((resolve, reject) => reject(source)).then(() => {}, () => {}).catch(x => sink(x)); // OK! + new Promise((resolve, reject) => reject(source)).then(() => { }, () => { }).catch(x => sink(x)); // OK! - Promise.resolve(source).catch(() => {}).catch(() => {}).catch(() => {}).then(a => sink(a)); // NOT OK! + Promise.resolve(source).catch(() => { }).catch(() => { }).catch(() => { }).then(a => sink(a)); // NOT OK! - Promise.resolve(source).finally(() => {}).then(a => sink(a)); // NOT OK! + Promise.resolve(source).finally(() => { }).then(a => sink(a)); // NOT OK! - new Promise(() => {throw source}).catch(x => sink(x)); // NOT OK! + new Promise(() => { throw source }).catch(x => sink(x)); // NOT OK! + + function createPromise(src) { + return Promise.resolve(src); + } + createPromise(source).then(v => sink(v)); // NOT OK! })(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/interflow.js b/javascript/ql/test/library-tests/Promises/interflow.js new file mode 100644 index 00000000000..836b18950af --- /dev/null +++ b/javascript/ql/test/library-tests/Promises/interflow.js @@ -0,0 +1,20 @@ +(function () { + function getSource() { + var source = "source"; // step 1 + return source; // step 2 + } + loadScript(getSource()) // step 3 + .then(function () { }) + .then(function () { }) + .catch(handleError); + function loadScript(src) { // step 4 (is summarized) + return new Promise(function (resolve, reject) { + setTimeout(function (error) { + reject(new Error('Blah: ' + src)); // step 5 + }, 1000); + }); + } + function handleError(error) { // step 6 + sink(error); // step 7 + } +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 80f6a918825..32f74a2b2a5 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -8,14 +8,15 @@ test_ResolvedPromiseDefinition | flow.js:36:11:36:33 | Promise ... source) | flow.js:36:27:36:32 | source | | flow.js:44:2:44:24 | Promise ... source) | flow.js:44:18:44:23 | source | | flow.js:46:2:46:24 | Promise ... source) | flow.js:46:18:46:23 | source | +| flow.js:51:10:51:29 | Promise.resolve(src) | flow.js:51:26:51:28 | src | | promises.js:53:19:53:41 | Promise ... source) | promises.js:53:35:53:40 | source | | promises.js:62:19:62:41 | Promise ... source) | promises.js:62:35:62:40 | source | | promises.js:71:5:71:27 | Promise ... source) | promises.js:71:21:71:26 | source | test_PromiseDefinition_getARejectHandler | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:69:26:80 | y => sink(y) | | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | -| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:66:42:73 | () => {} | -| flow.js:48:2:48:34 | new Pro ... ource}) | flow.js:48:42:48:53 | x => sink(x) | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:67:42:75 | () => { } | +| flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:20:6:22:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | @@ -28,7 +29,8 @@ test_PromiseDefinition_getExecutor | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:14:32:48 | (resolv ... source) | | flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:14:40:48 | (resolv ... source) | | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:14:42:48 | (resolv ... source) | -| flow.js:48:2:48:34 | new Pro ... ource}) | flow.js:48:14:48:33 | () => {throw source} | +| flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:14:48:35 | () => { ... ource } | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:31:35:5 | functio ... ;\\n } | @@ -44,7 +46,8 @@ test_PromiseDefinition | flow.js:32:2:32:49 | new Pro ... ource)) | | flow.js:40:2:40:49 | new Pro ... ource)) | | flow.js:42:2:42:49 | new Pro ... ource)) | -| flow.js:48:2:48:34 | new Pro ... ource}) | +| flow.js:48:2:48:36 | new Pro ... urce }) | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | | promises.js:33:19:35:6 | new Pro ... \\n }) | @@ -52,8 +55,8 @@ test_PromiseDefinition test_PromiseDefinition_getAResolveHandler | flow.js:24:2:24:49 | new Pro ... ource)) | flow.js:24:56:24:67 | x => sink(x) | | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:56:26:66 | x => foo(x) | -| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:56:40:63 | () => {} | -| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:56:42:63 | () => {} | +| flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:56:40:64 | () => { } | +| flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:56:42:64 | () => { } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | @@ -68,6 +71,7 @@ test_PromiseDefinition_getRejectParameter | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:24:32:29 | reject | | flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:24:40:29 | reject | | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:24:42:29 | reject | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:50:33:55 | reject | @@ -81,13 +85,14 @@ test_PromiseDefinition_getResolveParameter | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:15:32:21 | resolve | | flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:15:40:21 | resolve | | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:15:42:21 | resolve | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:41:33:47 | resolve | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:39:43:45 | resolve | test_PromiseDefinition_getACatchHandler | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | -| flow.js:48:2:48:34 | new Pro ... ource}) | flow.js:48:42:48:53 | x => sink(x) | +| flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | flow | flow.js:2:15:2:22 | "source" | flow.js:5:7:5:14 | await p1 | @@ -97,9 +102,11 @@ flow | flow.js:2:15:2:22 | "source" | flow.js:26:79:26:79 | y | | flow.js:2:15:2:22 | "source" | flow.js:28:58:28:58 | z | | flow.js:2:15:2:22 | "source" | flow.js:32:67:32:67 | x | -| flow.js:2:15:2:22 | "source" | flow.js:34:57:34:57 | a | +| flow.js:2:15:2:22 | "source" | flow.js:34:58:34:58 | a | | flow.js:2:15:2:22 | "source" | flow.js:38:29:38:29 | a | -| flow.js:2:15:2:22 | "source" | flow.js:40:82:40:82 | x | -| flow.js:2:15:2:22 | "source" | flow.js:44:89:44:89 | a | -| flow.js:2:15:2:22 | "source" | flow.js:46:59:46:59 | a | -| flow.js:2:15:2:22 | "source" | flow.js:48:52:48:52 | x | +| flow.js:2:15:2:22 | "source" | flow.js:40:83:40:83 | x | +| flow.js:2:15:2:22 | "source" | flow.js:44:92:44:92 | a | +| flow.js:2:15:2:22 | "source" | flow.js:46:60:46:60 | a | +| flow.js:2:15:2:22 | "source" | flow.js:48:54:48:54 | x | +| flow.js:2:15:2:22 | "source" | flow.js:53:39:53:39 | v | +| interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | From e08fc08337d309fc8bc4ada749515f15ae7552ca Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 15 Jan 2020 14:56:58 +0100 Subject: [PATCH 011/148] don't use pseudo-properties for resolved promise data-flow --- .../ql/src/semmle/javascript/Promises.qll | 84 +++++++------------ 1 file changed, 28 insertions(+), 56 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index f018724964f..6b534bcac9f 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -121,16 +121,9 @@ class AggregateES2015PromiseDefinition extends PromiseCreationCall { } /** - * This module defines how data-flow propagates into and out a Promise. + * This module defines how exceptional data-flow propagates into and out a Promise. */ -private module PromiseFlow { - /** - * Gets the pseudo-field used to describe resolved values in a promise. - */ - string resolveField() { - result = "$PromiseResolveField$" - } - +private module ExceptionalPromiseFlow { /** * Gets the pseudo-field used to describe rejected values in a promise. */ @@ -141,7 +134,7 @@ private module PromiseFlow { /** * A flow step describing a promise definition. * - * The resolved/rejected value is written to a pseudo-field on the promise. + * The rejected value is written to a pseudo-field on the promise. */ class PromiseDefitionStep extends DataFlow::AdditionalFlowStep { PromiseDefinition promise; @@ -150,10 +143,6 @@ private module PromiseFlow { } override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { - prop = resolveField() and - pred = promise.getResolveParameter().getACall().getArgument(0) and - succ = this - or prop = rejectField() and ( pred = promise.getRejectParameter().getACall().getArgument(0) or @@ -164,25 +153,8 @@ private module PromiseFlow { } /** - * A flow step describing the a Promise.resolve (and similar) call. - */ - class CreationStep extends DataFlow::AdditionalFlowStep { - PromiseCreationCall promise; - CreationStep() { - this = promise - } - - override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { - prop = resolveField() and - pred = promise.getValue() and - succ = this - } - } - - /** - * A load step loading the pseudo-field describing that the promise is either resolved or rejected. - * A resolved value is forwarding as the resulting value of the `await` expression, - * and a rejected value is thrown as a exception. + * A load step loading the pseudo-field describing that the promise is rejected. + * The rejected value is thrown as a exception. */ class AwaitStep extends DataFlow::AdditionalFlowStep { DataFlow::Node operand; @@ -193,10 +165,6 @@ private module PromiseFlow { } override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { - prop = resolveField() and - succ = this and - pred = operand.getALocalSource() - or prop = rejectField() and succ = await.getExceptionTarget() and pred = operand.getALocalSource() @@ -212,10 +180,6 @@ private module PromiseFlow { } override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { - prop = resolveField() and - pred = getReceiver().getALocalSource() and - succ = getCallback(0).getParameter(0) - or prop = rejectField() and pred = getReceiver().getALocalSource() and succ = getCallback(1).getParameter(0) @@ -229,10 +193,6 @@ private module PromiseFlow { } override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { - prop = resolveField() and - pred = getCallback([0..1]).getAReturn() and - succ = this - or prop = rejectField() and pred = getCallback([0..1]).getExceptionalReturn() and succ = this @@ -253,12 +213,6 @@ private module PromiseFlow { succ = getCallback(0).getParameter(0) } - override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { - prop = resolveField() and - pred = getReceiver().getALocalSource() and - succ = this - } - override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and pred = getCallback([0..1]).getExceptionalReturn() and @@ -275,7 +229,7 @@ private module PromiseFlow { } override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { - (prop = resolveField() or prop = rejectField()) and + prop = rejectField() and pred = getReceiver().getALocalSource() and succ = this } @@ -292,17 +246,35 @@ predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) { // from `x` to `Promise.resolve(x)` pred = succ.(PromiseCreationCall).getValue() or - exists(DataFlow::MethodCallNode thn, DataFlow::FunctionNode cb | - thn.getMethodName() = "then" and cb = thn.getCallback(0) + exists(DataFlow::MethodCallNode thn | + thn.getMethodName() = "then" | // from `p` to `x` in `p.then(x => ...)` pred = thn.getReceiver() and - succ = cb.getParameter(0) + succ = thn.getCallback(0).getParameter(0) or // from `v` to `p.then(x => return v)` - pred = cb.getAReturn() and + pred = thn.getCallback([0..1]).getAReturn() and succ = thn ) + or + // from `p` to `p.catch(..)` + exists(DataFlow::MethodCallNode catch | catch.getMethodName() = "catch" | + pred = catch.getReceiver() and + succ = catch + ) + or + // from `p` to `p.finally(..)` + exists(DataFlow::MethodCallNode finally | finally.getMethodName() = "finally" | + pred = finally.getReceiver() and + succ = finally + ) + or + // from `x` to `await x` + exists(AwaitExpr await | + pred.getEnclosingExpr() = await.getOperand() and + succ.getEnclosingExpr() = await + ) } /** From a76ab39a39392fcb737e7414d66acb093dbc4134 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 15 Jan 2020 16:00:57 +0100 Subject: [PATCH 012/148] no longer need for .getALocalSource() in custom load/store --- .../ql/src/semmle/javascript/Promises.qll | 10 +-- .../javascript/dataflow/Configuration.qll | 63 +++++++++++-------- .../ql/test/library-tests/Promises/flow.js | 5 ++ .../library-tests/Promises/tests.expected | 6 ++ 4 files changed, 52 insertions(+), 32 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 6b534bcac9f..9259f6f472f 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -167,7 +167,7 @@ private module ExceptionalPromiseFlow { override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and succ = await.getExceptionTarget() and - pred = operand.getALocalSource() + pred = operand } } @@ -181,14 +181,14 @@ private module ExceptionalPromiseFlow { override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and - pred = getReceiver().getALocalSource() and + pred = getReceiver() and succ = getCallback(1).getParameter(0) } override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { not exists(this.getArgument(1)) and prop = rejectField() and - pred = getReceiver().getALocalSource() and + pred = getReceiver() and succ = this } @@ -209,7 +209,7 @@ private module ExceptionalPromiseFlow { override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and - pred = getReceiver().getALocalSource() and + pred = getReceiver() and succ = getCallback(0).getParameter(0) } @@ -230,7 +230,7 @@ private module ExceptionalPromiseFlow { override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and - pred = getReceiver().getALocalSource() and + pred = getReceiver() and succ = this } } diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index 58f31aae0f7..a9a6affabe0 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -585,12 +585,9 @@ private predicate exploratoryFlowStep( basicStoreStep(pred, succ, _) or basicLoadStep(pred, succ, _) or - any(AdditionalFlowStep s).store(pred, succ, _) or - cfg.isAdditionalStoreStep(pred, succ, _) or - any(AdditionalFlowStep s).load(pred, succ, _) or - cfg.isAdditionalLoadStep(pred, succ, _) or - any(AdditionalFlowStep s).copyProperty(pred, succ, _) or - cfg.isAdditionalCopyPropertyStep(pred, succ, _) or + isAdditionalStoreStep(pred, succ, _, cfg) or + isAdditionalLoadStep(pred, succ, _, cfg) or + isAdditionalCopyPropertyStep(pred, succ, _ ,cfg) or // the following two disjuncts taken together over-approximate flow through // higher-order calls callback(pred, succ) or @@ -752,10 +749,7 @@ private predicate storeStep( basicStoreStep(pred, succ, prop) and summary = PathSummary::level() or - any(AdditionalFlowStep s).store(pred, succ, prop) and - summary = PathSummary::level() - or - cfg.isAdditionalStoreStep(pred, succ, prop) and + isAdditionalStoreStep(pred, succ, prop, cfg) and summary = PathSummary::level() or exists(Function f, DataFlow::Node mid | @@ -766,11 +760,7 @@ private predicate storeStep( returnedPropWrite(f, _, prop, mid) or exists(DataFlow::SourceNode base | - ( - any(AdditionalFlowStep step).store(mid, _, prop) - or - cfg.isAdditionalStoreStep(mid, _, prop) - ) + isAdditionalStoreStep(mid, _, prop, cfg) and base.flowsToExpr(f.getAReturnedExpr()) ) @@ -793,9 +783,7 @@ private predicate parameterPropRead( ( read = parm.(DataFlow::SourceNode).getAPropertyRead(prop) or - any(AdditionalFlowStep step).load(parm, read, prop) - or - cfg.isAdditionalLoadStep(parm, read, prop) + isAdditionalLoadStep(parm, read, prop, cfg) ) ) } @@ -818,6 +806,30 @@ private predicate reachesReturn( ) } +private predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { + exists(DataFlow::Node obj | pred = obj.getALocalSource() or pred = obj | + any(AdditionalFlowStep s).load(obj, succ, prop) + or + cfg.isAdditionalLoadStep(obj, succ, prop) + ) +} + +private predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { + exists(DataFlow::Node obj | pred = obj.getALocalSource() or pred = obj | + any(AdditionalFlowStep s).store(obj, succ, prop) + or + cfg.isAdditionalStoreStep(obj, succ, prop) + ) +} + +private predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { + exists(DataFlow::Node obj | pred = obj.getALocalSource() or pred = obj | + any(AdditionalFlowStep s).copyProperty(obj, succ, prop) + or + cfg.isAdditionalCopyPropertyStep(obj, succ, prop) + ) +} + /** * Holds if property `prop` of `pred` may flow into `succ` along a path summarized by * `summary`. @@ -829,10 +841,7 @@ private predicate loadStep( basicLoadStep(pred, succ, prop) and summary = PathSummary::level() or - any(AdditionalFlowStep s).load(pred, succ, prop) and - summary = PathSummary::level() - or - cfg.isAdditionalLoadStep(pred, succ, prop) and + isAdditionalLoadStep(pred, succ, prop, cfg) and summary = PathSummary::level() or exists(Function f, DataFlow::Node read | @@ -874,7 +883,7 @@ private predicate flowThroughProperty( ) { exists(string prop, DataFlow::Node storeBase, DataFlow::Node loadBase, PathSummary oldSummary, PathSummary newSummary | reachableFromStoreBase(prop, pred, storeBase, cfg, oldSummary) and - (storeBase = loadBase or existsCopyProperty(storeBase, loadBase, prop)) and + (storeBase = loadBase or existsCopyProperty(storeBase, loadBase, prop, cfg)) and loadStep(loadBase, succ, prop, cfg, newSummary) and summary = oldSummary.append(newSummary) ) @@ -886,11 +895,11 @@ private predicate flowThroughProperty( * The recursion of this predicate has been unfolded once compared to a naive implementation in order to avoid having no constraint on `prop`. * Therefore a caller of this predicate should also test whether the `toNode` and `fromNode` are equal. */ -private predicate existsCopyProperty(DataFlow::Node fromNode, DataFlow::Node toNode, string prop) { - exists(DataFlow::AdditionalFlowStep step, DataFlow::Node mid | - step.copyProperty(fromNode, mid, prop) and +private predicate existsCopyProperty(DataFlow::Node fromNode, DataFlow::Node toNode, string prop, DataFlow::Configuration cfg) { + exists(DataFlow::Node mid | + isAdditionalCopyPropertyStep(fromNode, mid, prop, cfg) and ( - existsCopyProperty(mid, toNode, prop) + existsCopyProperty(mid, toNode, prop, cfg) or mid = toNode ) diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js index 3d6bd556bdd..fa6be1d9801 100644 --- a/javascript/ql/test/library-tests/Promises/flow.js +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -51,4 +51,9 @@ return Promise.resolve(src); } createPromise(source).then(v => sink(v)); // NOT OK! + + var p8 = new Promise((resolve, reject) => reject(source)); + var p9 = p8.then(() => {}); + var p10 = p9.finally(() => {}); + p10.catch((x) => sink(x)); // NOT OK! })(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 32f74a2b2a5..9e1162f743f 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -30,6 +30,7 @@ test_PromiseDefinition_getExecutor | flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:14:40:48 | (resolv ... source) | | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:14:42:48 | (resolv ... source) | | flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:14:48:35 | () => { ... ource } | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:23:55:57 | (resolv ... source) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | @@ -47,6 +48,7 @@ test_PromiseDefinition | flow.js:40:2:40:49 | new Pro ... ource)) | | flow.js:42:2:42:49 | new Pro ... ource)) | | flow.js:48:2:48:36 | new Pro ... urce }) | +| flow.js:55:11:55:58 | new Pro ... ource)) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | @@ -57,6 +59,7 @@ test_PromiseDefinition_getAResolveHandler | flow.js:26:2:26:49 | new Pro ... ource)) | flow.js:26:56:26:66 | x => foo(x) | | flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:56:40:64 | () => { } | | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:56:42:64 | () => { } | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:56:19:56:26 | () => {} | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | @@ -71,6 +74,7 @@ test_PromiseDefinition_getRejectParameter | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:24:32:29 | reject | | flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:24:40:29 | reject | | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:24:42:29 | reject | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:33:55:38 | reject | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | @@ -85,6 +89,7 @@ test_PromiseDefinition_getResolveParameter | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:15:32:21 | resolve | | flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:15:40:21 | resolve | | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:15:42:21 | resolve | +| flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:24:55:30 | resolve | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | @@ -109,4 +114,5 @@ flow | flow.js:2:15:2:22 | "source" | flow.js:46:60:46:60 | a | | flow.js:2:15:2:22 | "source" | flow.js:48:54:48:54 | x | | flow.js:2:15:2:22 | "source" | flow.js:53:39:53:39 | v | +| flow.js:2:15:2:22 | "source" | flow.js:58:24:58:24 | x | | interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | From 9998059d59a722a7777cbc2300658876f96f38d2 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 16 Jan 2020 14:16:04 +0100 Subject: [PATCH 013/148] add pragma to fix performance (same issue as in #2512) --- javascript/ql/src/semmle/javascript/dataflow/Configuration.qll | 1 + 1 file changed, 1 insertion(+) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index a9a6affabe0..4b0556b6b7f 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -318,6 +318,7 @@ abstract class BarrierGuardNode extends DataFlow::Node { * * INTERNAL: this predicate should only be used from within `blocks(boolean, Expr)`. */ + pragma[noinline,nomagic] predicate internalBlocks(DataFlow::Node nd, string label) { // 1) `nd` is a use of a refinement node that blocks its input variable exists(SsaRefinementNode ref, boolean outcome | From 06e898f53b96d581d2eedacfe5a972d3db37a4f8 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 16 Jan 2020 15:11:35 +0100 Subject: [PATCH 014/148] only use .getALocalSource in copyPropertyStep --- .../ql/src/semmle/javascript/Promises.qll | 10 ++--- .../javascript/dataflow/Configuration.qll | 43 ++++++++++--------- .../ql/test/library-tests/Promises/flow.js | 13 ++++++ .../library-tests/Promises/tests.expected | 11 +++++ 4 files changed, 52 insertions(+), 25 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 9259f6f472f..576b411fce1 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -142,7 +142,7 @@ private module ExceptionalPromiseFlow { this = promise } - override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { prop = rejectField() and ( pred = promise.getRejectParameter().getACall().getArgument(0) or @@ -185,14 +185,14 @@ private module ExceptionalPromiseFlow { succ = getCallback(1).getParameter(0) } - override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate copyProperty(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { not exists(this.getArgument(1)) and prop = rejectField() and pred = getReceiver() and succ = this } - override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { prop = rejectField() and pred = getCallback([0..1]).getExceptionalReturn() and succ = this @@ -213,7 +213,7 @@ private module ExceptionalPromiseFlow { succ = getCallback(0).getParameter(0) } - override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { prop = rejectField() and pred = getCallback([0..1]).getExceptionalReturn() and succ = this @@ -228,7 +228,7 @@ private module ExceptionalPromiseFlow { this.getMethodName() = "finally" } - override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate copyProperty(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { prop = rejectField() and pred = getReceiver() and succ = this diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index 4b0556b6b7f..179cf33e23d 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -225,9 +225,11 @@ abstract class Configuration extends string { } /** - * Holds if the `pred` should be stored in the object `succ` under the property `prop`. + * Holds if the `pred` should be stored in the object `succ` under the property `prop`. + * + * `succ` is a DataFlow::SourceNode, as this is assumed by the `isAdditionalCopyPropertyStep` predicate. */ - predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() } /** * Holds if the property `prop` of the object `pred` should be loaded into `succ`. @@ -237,7 +239,7 @@ abstract class Configuration extends string { /** * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. */ - predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() } } /** @@ -468,9 +470,11 @@ abstract class AdditionalFlowStep extends DataFlow::Node { /** * Holds if the `pred` should be stored in the object `succ` under the property `prop`. + * + * `succ` is a DataFlow::SourceNode, as this is assumed by the `copyProperty` predicate. */ cached - predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() } /** * Holds if the property `prop` of the object `pred` should be loaded into `succ`. @@ -482,7 +486,7 @@ abstract class AdditionalFlowStep extends DataFlow::Node { * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. */ cached - predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + predicate copyProperty(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() } } /** @@ -808,26 +812,25 @@ private predicate reachesReturn( } private predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { - exists(DataFlow::Node obj | pred = obj.getALocalSource() or pred = obj | - any(AdditionalFlowStep s).load(obj, succ, prop) - or - cfg.isAdditionalLoadStep(obj, succ, prop) - ) + any(AdditionalFlowStep s).load(pred, succ, prop) + or + cfg.isAdditionalLoadStep(pred, succ, prop) } -private predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { - exists(DataFlow::Node obj | pred = obj.getALocalSource() or pred = obj | - any(AdditionalFlowStep s).store(obj, succ, prop) - or - cfg.isAdditionalStoreStep(obj, succ, prop) - ) +private predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop, DataFlow::Configuration cfg) { + any(AdditionalFlowStep s).store(pred, succ, prop) + or + cfg.isAdditionalStoreStep(pred, succ, prop) } -private predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { - exists(DataFlow::Node obj | pred = obj.getALocalSource() or pred = obj | - any(AdditionalFlowStep s).copyProperty(obj, succ, prop) +private predicate isAdditionalCopyPropertyStep(DataFlow::SourceNode pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { + exists(DataFlow::Node predNode, DataFlow::SourceNode succNode | + pred = predNode.getALocalSource() and + succ.getALocalSource() = succNode + | + any(AdditionalFlowStep s).copyProperty(predNode, succNode, prop) or - cfg.isAdditionalCopyPropertyStep(obj, succ, prop) + cfg.isAdditionalCopyPropertyStep(predNode, succNode, prop) ) } diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js index fa6be1d9801..ceea0b3f34f 100644 --- a/javascript/ql/test/library-tests/Promises/flow.js +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -56,4 +56,17 @@ var p9 = p8.then(() => {}); var p10 = p9.finally(() => {}); p10.catch((x) => sink(x)); // NOT OK! + + var p11 = new Promise((resolve, reject) => reject(source)); + var p12 = p11.then(() => {}); + p12.catch(x => sink(x)); // NOT OK! + + async function throws() { + await new Promise((resolve, reject) => reject(source)); + } + try { + throws(); + } catch(e) { + sink(e); // NOT OK! + } })(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 9e1162f743f..20cc571e18d 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -31,6 +31,8 @@ test_PromiseDefinition_getExecutor | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:14:42:48 | (resolv ... source) | | flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:14:48:35 | () => { ... ource } | | flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:23:55:57 | (resolv ... source) | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:24:60:58 | (resolv ... source) | +| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:21:65:55 | (resolv ... source) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | @@ -49,6 +51,8 @@ test_PromiseDefinition | flow.js:42:2:42:49 | new Pro ... ource)) | | flow.js:48:2:48:36 | new Pro ... urce }) | | flow.js:55:11:55:58 | new Pro ... ource)) | +| flow.js:60:12:60:59 | new Pro ... ource)) | +| flow.js:65:9:65:56 | new Pro ... ource)) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | @@ -60,6 +64,7 @@ test_PromiseDefinition_getAResolveHandler | flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:56:40:64 | () => { } | | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:56:42:64 | () => { } | | flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:56:19:56:26 | () => {} | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:61:21:61:28 | () => {} | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | @@ -75,6 +80,8 @@ test_PromiseDefinition_getRejectParameter | flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:24:40:29 | reject | | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:24:42:29 | reject | | flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:33:55:38 | reject | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:34:60:39 | reject | +| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:31:65:36 | reject | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | @@ -90,6 +97,8 @@ test_PromiseDefinition_getResolveParameter | flow.js:40:2:40:49 | new Pro ... ource)) | flow.js:40:15:40:21 | resolve | | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:15:42:21 | resolve | | flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:24:55:30 | resolve | +| flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:25:60:31 | resolve | +| flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:22:65:28 | resolve | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | @@ -115,4 +124,6 @@ flow | flow.js:2:15:2:22 | "source" | flow.js:48:54:48:54 | x | | flow.js:2:15:2:22 | "source" | flow.js:53:39:53:39 | v | | flow.js:2:15:2:22 | "source" | flow.js:58:24:58:24 | x | +| flow.js:2:15:2:22 | "source" | flow.js:62:22:62:22 | x | +| flow.js:2:15:2:22 | "source" | flow.js:70:8:70:8 | e | | interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | From 6ad62e32e0b97229806f051631bd8cf9e556cbf2 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 17 Jan 2020 12:01:02 +0100 Subject: [PATCH 015/148] copyPropertyStep works interprocedurally --- .../ql/src/semmle/javascript/Promises.qll | 10 +-- .../javascript/dataflow/Configuration.qll | 72 +++++++++++-------- .../ql/test/library-tests/Promises/flow.js | 5 ++ .../library-tests/Promises/tests.expected | 6 ++ 4 files changed, 57 insertions(+), 36 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 576b411fce1..9259f6f472f 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -142,7 +142,7 @@ private module ExceptionalPromiseFlow { this = promise } - override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { + override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and ( pred = promise.getRejectParameter().getACall().getArgument(0) or @@ -185,14 +185,14 @@ private module ExceptionalPromiseFlow { succ = getCallback(1).getParameter(0) } - override predicate copyProperty(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { + override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { not exists(this.getArgument(1)) and prop = rejectField() and pred = getReceiver() and succ = this } - override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { + override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and pred = getCallback([0..1]).getExceptionalReturn() and succ = this @@ -213,7 +213,7 @@ private module ExceptionalPromiseFlow { succ = getCallback(0).getParameter(0) } - override predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { + override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and pred = getCallback([0..1]).getExceptionalReturn() and succ = this @@ -228,7 +228,7 @@ private module ExceptionalPromiseFlow { this.getMethodName() = "finally" } - override predicate copyProperty(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { + override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and pred = getReceiver() and succ = this diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index 179cf33e23d..ad9202527d7 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -226,10 +226,8 @@ abstract class Configuration extends string { /** * Holds if the `pred` should be stored in the object `succ` under the property `prop`. - * - * `succ` is a DataFlow::SourceNode, as this is assumed by the `isAdditionalCopyPropertyStep` predicate. */ - predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() } + predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } /** * Holds if the property `prop` of the object `pred` should be loaded into `succ`. @@ -237,9 +235,9 @@ abstract class Configuration extends string { predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } /** - * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. + * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. */ - predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() } + predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } } /** @@ -470,11 +468,9 @@ abstract class AdditionalFlowStep extends DataFlow::Node { /** * Holds if the `pred` should be stored in the object `succ` under the property `prop`. - * - * `succ` is a DataFlow::SourceNode, as this is assumed by the `copyProperty` predicate. */ cached - predicate store(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() } + predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } /** * Holds if the property `prop` of the object `pred` should be loaded into `succ`. @@ -486,7 +482,7 @@ abstract class AdditionalFlowStep extends DataFlow::Node { * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. */ cached - predicate copyProperty(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() } + predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } } /** @@ -765,7 +761,7 @@ private predicate storeStep( returnedPropWrite(f, _, prop, mid) or exists(DataFlow::SourceNode base | - isAdditionalStoreStep(mid, _, prop, cfg) + isAdditionalStoreStep(mid, base, prop, cfg) and base.flowsToExpr(f.getAReturnedExpr()) ) @@ -811,27 +807,31 @@ private predicate reachesReturn( ) } +/** + * Holds if the property `prop` of the object `pred` should be loaded into `succ`. + */ private predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { any(AdditionalFlowStep s).load(pred, succ, prop) or cfg.isAdditionalLoadStep(pred, succ, prop) } -private predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop, DataFlow::Configuration cfg) { +/** + * Holds if the `pred` should be stored in the object `succ` under the property `prop`. + */ +private predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { any(AdditionalFlowStep s).store(pred, succ, prop) or cfg.isAdditionalStoreStep(pred, succ, prop) } -private predicate isAdditionalCopyPropertyStep(DataFlow::SourceNode pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { - exists(DataFlow::Node predNode, DataFlow::SourceNode succNode | - pred = predNode.getALocalSource() and - succ.getALocalSource() = succNode - | - any(AdditionalFlowStep s).copyProperty(predNode, succNode, prop) - or - cfg.isAdditionalCopyPropertyStep(predNode, succNode, prop) - ) +/** + * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. + */ +private predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { + any(AdditionalFlowStep s).copyProperty(pred, succ, prop) + or + cfg.isAdditionalCopyPropertyStep(pred, succ, prop) } /** @@ -869,7 +869,12 @@ private predicate reachableFromStoreBase( or exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary | reachableFromStoreBase(prop, rhs, mid, cfg, oldSummary) and - flowStep(mid, cfg, nd, newSummary) and + ( + flowStep(mid, cfg, nd, newSummary) + or + existsCopyProperty(mid, nd, prop, cfg) and + newSummary = PathSummary::level() + ) and summary = oldSummary.appendValuePreserving(newSummary) ) } @@ -885,28 +890,33 @@ pragma[noinline] private predicate flowThroughProperty( DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg, PathSummary summary ) { - exists(string prop, DataFlow::Node storeBase, DataFlow::Node loadBase, PathSummary oldSummary, PathSummary newSummary | - reachableFromStoreBase(prop, pred, storeBase, cfg, oldSummary) and - (storeBase = loadBase or existsCopyProperty(storeBase, loadBase, prop, cfg)) and - loadStep(loadBase, succ, prop, cfg, newSummary) and + exists(string prop, DataFlow::Node base, PathSummary oldSummary, PathSummary newSummary | + reachableFromStoreBase(prop, pred, base, cfg, oldSummary) and + loadStep(base, succ, prop, cfg, newSummary) and summary = oldSummary.append(newSummary) ) } +/** + * Holds if the property `prop` is copied from `fromNode` to `toNode`. + */ +bindingset[prop, cfg] +private predicate existsCopyProperty(DataFlow::Node fromNode, DataFlow::Node toNode, string prop, DataFlow::Configuration cfg) { + fromNode = toNode + or + existsCopyPropertyRecursive(fromNode, toNode, prop, cfg) +} + /** * Holds if the property `prop` is copied from `fromNode` to `toNode` using at least 1 step. * * The recursion of this predicate has been unfolded once compared to a naive implementation in order to avoid having no constraint on `prop`. * Therefore a caller of this predicate should also test whether the `toNode` and `fromNode` are equal. */ -private predicate existsCopyProperty(DataFlow::Node fromNode, DataFlow::Node toNode, string prop, DataFlow::Configuration cfg) { +private predicate existsCopyPropertyRecursive(DataFlow::Node fromNode, DataFlow::Node toNode, string prop, DataFlow::Configuration cfg) { exists(DataFlow::Node mid | isAdditionalCopyPropertyStep(fromNode, mid, prop, cfg) and - ( - existsCopyProperty(mid, toNode, prop, cfg) - or - mid = toNode - ) + existsCopyProperty(mid, toNode, prop, cfg) ) } diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js index ceea0b3f34f..1ef55b3a548 100644 --- a/javascript/ql/test/library-tests/Promises/flow.js +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -69,4 +69,9 @@ } catch(e) { sink(e); // NOT OK! } + + function chainedPromise() { + return new Promise((resolve, reject) => reject(source)).then(() => {}); + } + chainedPromise().then(() => {}).catch(e => sink(e)); // NOT OK! })(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 20cc571e18d..cc452dbe62d 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -33,6 +33,7 @@ test_PromiseDefinition_getExecutor | flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:23:55:57 | (resolv ... source) | | flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:24:60:58 | (resolv ... source) | | flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:21:65:55 | (resolv ... source) | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:22:74:56 | (resolv ... source) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | @@ -53,6 +54,7 @@ test_PromiseDefinition | flow.js:55:11:55:58 | new Pro ... ource)) | | flow.js:60:12:60:59 | new Pro ... ource)) | | flow.js:65:9:65:56 | new Pro ... ource)) | +| flow.js:74:10:74:57 | new Pro ... ource)) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | @@ -65,6 +67,7 @@ test_PromiseDefinition_getAResolveHandler | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:56:42:64 | () => { } | | flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:56:19:56:26 | () => {} | | flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:61:21:61:28 | () => {} | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:64:74:71 | () => {} | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | @@ -82,6 +85,7 @@ test_PromiseDefinition_getRejectParameter | flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:33:55:38 | reject | | flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:34:60:39 | reject | | flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:31:65:36 | reject | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:32:74:37 | reject | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | @@ -99,6 +103,7 @@ test_PromiseDefinition_getResolveParameter | flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:55:24:55:30 | resolve | | flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:25:60:31 | resolve | | flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:22:65:28 | resolve | +| flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:23:74:29 | resolve | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | @@ -126,4 +131,5 @@ flow | flow.js:2:15:2:22 | "source" | flow.js:58:24:58:24 | x | | flow.js:2:15:2:22 | "source" | flow.js:62:22:62:22 | x | | flow.js:2:15:2:22 | "source" | flow.js:70:8:70:8 | e | +| flow.js:2:15:2:22 | "source" | flow.js:76:50:76:50 | e | | interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | From a25c5d70904cb06fdf108e49fcb166435a07e7bd Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 17 Jan 2020 13:42:08 +0100 Subject: [PATCH 016/148] outlining a predicate to give hints about join ordering --- .../javascript/dataflow/Configuration.qll | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index ad9202527d7..24aef92d6f4 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -318,16 +318,12 @@ abstract class BarrierGuardNode extends DataFlow::Node { * * INTERNAL: this predicate should only be used from within `blocks(boolean, Expr)`. */ - pragma[noinline,nomagic] predicate internalBlocks(DataFlow::Node nd, string label) { // 1) `nd` is a use of a refinement node that blocks its input variable exists(SsaRefinementNode ref, boolean outcome | nd = DataFlow::ssaDefinitionNode(ref) and - forex(SsaVariable input | input = ref.getAnInput() | - getEnclosingExpr() = ref.getGuard().getTest() and - outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and - barrierGuardBlocksExpr(this, outcome, input.getAUse(), label) - ) + outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and + ssaRefinementBlocks(outcome, ref, label) ) or // 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p` @@ -337,8 +333,21 @@ abstract class BarrierGuardNode extends DataFlow::Node { outcome = cond.getOutcome() and barrierGuardBlocksAccessPath(this, outcome, p, label) and cond.dominates(bb) + ) + } + + /** + * Holds if there exists an input variable of `ref` that blocks the label `label`. + * + * This predicate is outlined to give the optimizer a hint about the join ordering. + */ + private predicate ssaRefinementBlocks(boolean outcome, SsaRefinementNode ref, string label) { + getEnclosingExpr() = ref.getGuard().getTest() and + forex(SsaVariable input | input = ref.getAnInput() | + barrierGuardBlocksExpr(this, outcome, input.getAUse(), label) ) } + /** * Holds if this node blocks expression `e` provided it evaluates to `outcome`. From 8cec46342f6e945069e189b5a032f0e036968e9f Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Sat, 18 Jan 2020 17:14:22 +0100 Subject: [PATCH 017/148] Query to detect LDAP injections in Java Refactoring --- .../Security/CWE/CWE-90/LdapInjectionLib.qll | 327 +++++------------- .../code/java/frameworks/ApacheLdap.qll | 29 ++ .../src/semmle/code/java/frameworks/Jndi.qll | 59 ++++ .../code/java/frameworks/SpringLdap.qll | 191 ++++++++++ .../semmle/code/java/frameworks/UnboundId.qll | 113 ++++++ 5 files changed, 476 insertions(+), 243 deletions(-) create mode 100644 java/ql/src/semmle/code/java/frameworks/ApacheLdap.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/Jndi.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/SpringLdap.qll create mode 100644 java/ql/src/semmle/code/java/frameworks/UnboundId.qll diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll index 4a78d8282bc..acda7e9aad1 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll @@ -1,129 +1,16 @@ import java import semmle.code.java.dataflow.FlowSources import DataFlow +import semmle.code.java.frameworks.Jndi +import semmle.code.java.frameworks.UnboundId +import semmle.code.java.frameworks.SpringLdap +import semmle.code.java.frameworks.ApacheLdap -/** The interface `javax.naming.directory.DirContext`. */ -class TypeDirContext extends Interface { - TypeDirContext() { this.hasQualifiedName("javax.naming.directory", "DirContext") } -} - -/** The interface `javax.naming.ldap.LdapContext`. */ -class TypeLdapContext extends Interface { - TypeLdapContext() { this.hasQualifiedName("javax.naming.ldap", "LdapContext") } -} - -/** The class `javax.naming.ldap.LdapName`. */ -class TypeLdapName extends Class { - TypeLdapName() { this.hasQualifiedName("javax.naming.ldap", "LdapName") } -} - -/** The interface `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */ -class TypeReadOnlySearchRequest extends Interface { - TypeReadOnlySearchRequest() { - this.hasQualifiedName("com.unboundid.ldap.sdk", "ReadOnlySearchRequest") - } -} - -/** The class `com.unboundid.ldap.sdk.SearchRequest`. */ -class TypeUnboundIdSearchRequest extends Class { - TypeUnboundIdSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") } -} - -/** The class `com.unboundid.ldap.sdk.Filter`. */ -class TypeUnboundIdLdapFilter extends Class { - TypeUnboundIdLdapFilter() { this.hasQualifiedName("com.unboundid.ldap.sdk", "Filter") } -} - -/** The class `com.unboundid.ldap.sdk.LDAPConnection`. */ -class TypeLDAPConnection extends Class { - TypeLDAPConnection() { this.hasQualifiedName("com.unboundid.ldap.sdk", "LDAPConnection") } -} - -/** The class `org.springframework.ldap.core.LdapTemplate`. */ -class TypeLdapTemplate extends Class { - TypeLdapTemplate() { this.hasQualifiedName("org.springframework.ldap.core", "LdapTemplate") } -} - -/** The interface `org.springframework.ldap.query.LdapQuery`. */ -class TypeLdapQuery extends Interface { - TypeLdapQuery() { this.hasQualifiedName("org.springframework.ldap.query", "LdapQuery") } -} - -/** The class `org.springframework.ldap.query.LdapQueryBuilder`. */ -class TypeLdapQueryBuilder extends Class { - TypeLdapQueryBuilder() { - this.hasQualifiedName("org.springframework.ldap.query", "LdapQueryBuilder") - } -} - -/** The interface `org.springframework.ldap.query.ConditionCriteria`. */ -class TypeConditionCriteria extends Interface { - TypeConditionCriteria() { - this.hasQualifiedName("org.springframework.ldap.query", "ConditionCriteria") - } -} - -/** The interface `org.springframework.ldap.query.ContainerCriteria`. */ -class TypeContainerCriteria extends Interface { - TypeContainerCriteria() { - this.hasQualifiedName("org.springframework.ldap.query", "ContainerCriteria") - } -} - -/** The class `org.springframework.ldap.filter.HardcodedFilter`. */ -class TypeHardcodedFilter extends Class { - TypeHardcodedFilter() { - this.hasQualifiedName("org.springframework.ldap.filter", "HardcodedFilter") - } -} - -/** The interface `org.springframework.ldap.filter.Filter`. */ -class TypeSpringLdapFilter extends Interface { - TypeSpringLdapFilter() { this.hasQualifiedName("org.springframework.ldap.filter", "Filter") } -} - -/** The class `org.springframework.ldap.support.LdapNameBuilder`. */ -class TypeLdapNameBuilder extends Class { - TypeLdapNameBuilder() { - this.hasQualifiedName("org.springframework.ldap.support", "LdapNameBuilder") - } -} - -/** The class `org.springframework.ldap.support.LdapUtils`. */ -class TypeLdapUtils extends Class { - TypeLdapUtils() { this.hasQualifiedName("org.springframework.ldap.support", "LdapUtils") } -} - -/** The interface `org.apache.directory.ldap.client.api.LdapConnection`. */ -class TypeLdapConnection extends Interface { - TypeLdapConnection() { - this.hasQualifiedName("org.apache.directory.ldap.client.api", "LdapConnection") - } -} - -/** The interface `org.apache.directory.api.ldap.model.message.SearchRequest`. */ -class TypeApacheSearchRequest extends Interface { - TypeApacheSearchRequest() { - this.hasQualifiedName("org.apache.directory.api.ldap.model.message", "SearchRequest") - } -} - -/** The class `org.apache.directory.api.ldap.model.name.Dn`. */ -class TypeApacheDn extends Class { - TypeApacheDn() { - this.hasQualifiedName("org.apache.directory.api.ldap.model.name", "Dn") - } -} - -/** The class `org.springframework.ldap.support.LdapEncoder`. */ -class TypeLdapEncoder extends Class { - TypeLdapEncoder() { this.hasQualifiedName("org.springframework.ldap.support", "LdapEncoder") } -} /** Holds if the parameter of `c` at index `paramIndex` is varargs. */ bindingset[paramIndex] predicate isVarargs(Callable c, int paramIndex) { - c.getParameter(min(int i | i = paramIndex or i = c.getNumberOfParameters() - 1 | i)).isVarargs() + c.isVarargs() and paramIndex >= c.getNumberOfParameters() - 1 } /** A data flow source for unvalidated user input that is used to construct LDAP queries. */ @@ -132,9 +19,6 @@ abstract class LdapInjectionSource extends DataFlow::Node { } /** A data flow sink for unvalidated user input that is used to construct LDAP queries. */ abstract class LdapInjectionSink extends DataFlow::ExprNode { } -/** A sanitizer for unvalidated user input that is used to construct LDAP queries. */ -abstract class LdapInjectionSanitizer extends DataFlow::ExprNode { } - /** * A taint-tracking configuration for unvalidated user input that is used to construct LDAP queries. */ @@ -145,7 +29,9 @@ class LdapInjectionFlowConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof LdapInjectionSink } - override predicate isSanitizer(DataFlow::Node node) { node instanceof LdapInjectionSanitizer } + override predicate isSanitizer(DataFlow::Node node) { + node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType + } override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) { ldapNameStep(node1, node2) or @@ -183,7 +69,7 @@ class LocalSource extends LdapInjectionSource { /** * JNDI sink for LDAP injection vulnerabilities, i.e. 1st (DN) or 2nd (filter) argument to - * `search` method from `DirContext` or `LdapContext`. + * `search` method from `DirContext`. */ class JndiLdapInjectionSink extends LdapInjectionSink { JndiLdapInjectionSink() { @@ -191,10 +77,7 @@ class JndiLdapInjectionSink extends LdapInjectionSink { ma.getMethod() = m and ma.getArgument(index) = this.getExpr() | - ( - m.getDeclaringType().getAnAncestor() instanceof TypeDirContext or - m.getDeclaringType().getAnAncestor() instanceof TypeLdapContext - ) and + m.getDeclaringType().getAnAncestor() instanceof TypeDirContext and m.hasName("search") and index in [0..1] ) @@ -213,8 +96,11 @@ class UnboundIdLdapInjectionSink extends LdapInjectionSink { m.getParameter(index) = param | // LDAPConnection.search, LDAPConnection.asyncSearch or LDAPConnection.searchForEntry method - m.getDeclaringType() instanceof TypeLDAPConnection and - (m.hasName("search") or m.hasName("asyncSearch") or m.hasName("searchForEntry")) and + ( + m instanceof MethodUnboundIdLDAPConnectionSearch or + m instanceof MethodUnboundIdLDAPConnectionAsyncSearch or + m instanceof MethodUnboundIdLDAPConnectionSearchForEntry + ) and // Parameter is not varargs not isVarargs(m, index) ) @@ -233,20 +119,26 @@ class SpringLdapInjectionSink extends LdapInjectionSink { m.getParameterType(index) = paramType | // LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method - m.getDeclaringType() instanceof TypeLdapTemplate and ( - m.hasName("authenticate") or - m.hasName("find") or - m.hasName("findOne") or - m.hasName("search") or - m.hasName("searchForContext") or - m.hasName("searchForObject") + m instanceof MethodSpringLdapTemplateAuthenticate or + m instanceof MethodSpringLdapTemplateFind or + m instanceof MethodSpringLdapTemplateFindOne or + m instanceof MethodSpringLdapTemplateSearch or + m instanceof MethodSpringLdapTemplateSearchForContext or + m instanceof MethodSpringLdapTemplateSearchForObject ) and ( // Parameter index is 1 (DN or query) or 2 (filter) if method is not authenticate - (index in [0..1] and not m.hasName("authenticate")) or + ( + index in [0..1] and + not m instanceof MethodSpringLdapTemplateAuthenticate + ) or // But it's not the last parameter in case of authenticate method (last param is password) - (index in [0..1] and index < m.getNumberOfParameters() - 1 and m.hasName("authenticate")) + ( + index in [0..1] and + index < m.getNumberOfParameters() - 1 and + m instanceof MethodSpringLdapTemplateAuthenticate + ) ) ) } @@ -260,46 +152,13 @@ class ApacheLdapInjectionSink extends LdapInjectionSink { ma.getArgument(index) = this.getExpr() and m.getParameterType(index) = paramType | - m.getDeclaringType().getAnAncestor() instanceof TypeLdapConnection and + m.getDeclaringType().getAnAncestor() instanceof TypeApacheLdapConnection and m.hasName("search") and not isVarargs(m, index) ) } } -/** An expression node with a primitive type. */ -class PrimitiveTypeSanitizer extends LdapInjectionSanitizer { - PrimitiveTypeSanitizer() { this.getType() instanceof PrimitiveType } -} - -/** An expression node with a boxed type. */ -class BoxedTypeSanitizer extends LdapInjectionSanitizer { - BoxedTypeSanitizer() { this.getType() instanceof BoxedType } -} - -/** encodeForLDAP and encodeForDN from OWASP ESAPI. */ -class EsapiSanitizer extends LdapInjectionSanitizer { - EsapiSanitizer() { - this.getExpr().(MethodAccess).getMethod().hasName("encodeForLDAP") - } -} - -/** LdapEncoder.filterEncode and LdapEncoder.nameEncode from Spring LDAP. */ -class SpringLdapSanitizer extends LdapInjectionSanitizer { - SpringLdapSanitizer() { - this.getType() instanceof TypeLdapEncoder and - this.getExpr().(MethodAccess).getMethod().hasName("filterEncode") - } -} - -/** Filter.encodeValue from UnboundID. */ -class UnboundIdSanitizer extends LdapInjectionSanitizer { - UnboundIdSanitizer() { - this.getType() instanceof TypeUnboundIdLdapFilter and - this.getExpr().(MethodAccess).getMethod().hasName("encodeValue") - } -} - /** * Holds if `n1` to `n2` is a dataflow step that converts between `String` and `LdapName`, * i.e. `new LdapName(tainted)`. @@ -316,13 +175,11 @@ predicate ldapNameStep(ExprNode n1, ExprNode n2) { * i.e. `new LdapName().addAll(tainted)`. */ predicate ldapNameAddAllStep(ExprNode n1, ExprNode n2) { - exists(MethodAccess ma, Method m | + exists(MethodAccess ma | n1.asExpr() = ma.getAnArgument() and (n2.asExpr() = ma or n2.asExpr() = ma.getQualifier()) | - ma.getMethod() = m and - m.getDeclaringType() instanceof TypeLdapName and - m.hasName("addAll") + ma.getMethod() instanceof MethodLdapNameAddAll ) } @@ -334,11 +191,13 @@ predicate ldapNameAddAllStep(ExprNode n1, ExprNode n2) { predicate ldapNameGetCloneStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and - n2.asExpr() = ma + n2.asExpr() = ma and + ma.getMethod() = m | - ma.getMethod() = m and - m.getDeclaringType() instanceof TypeLdapName and - (m.hasName("clone") or m.hasName("getAll") or m.hasName("getRdns") or m.hasName("toString")) + m instanceof MethodLdapNameClone or + m instanceof MethodLdapNameGetAll or + m instanceof MethodLdapNameGetRdns or + m instanceof MethodLdapNameToString ) } @@ -348,18 +207,15 @@ predicate ldapNameGetCloneStep(ExprNode n1, ExprNode n2) { */ predicate filterStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m | - n1.asExpr() = ma.getAnArgument() - | + n1.asExpr() = ma.getAnArgument() and n2.asExpr() = ma and - ma.getMethod() = m and - m.getDeclaringType() instanceof TypeUnboundIdLdapFilter and - ( - m.hasName("create") or - m.hasName("createANDFilter") or - m.hasName("createNOTFilter") or - m.hasName("createORFilter") or - m.hasName("simplifyFilter") - ) + ma.getMethod() = m + | + m instanceof MethodUnboundIdFilterCreate or + m instanceof MethodUnboundIdFilterCreateANDFilter or + m instanceof MethodUnboundIdFilterCreateNOTFilter or + m instanceof MethodUnboundIdFilterCreateORFilter or + m instanceof MethodUnboundIdFilterSimplifyFilter ) } @@ -383,13 +239,12 @@ predicate filterToStringStep(ExprNode n1, ExprNode n2) { * `SearchRequest`, i.e. `new SearchRequest(tainted)`. */ predicate unboundIdSearchRequestStep(ExprNode n1, ExprNode n2) { - exists(ConstructorCall cc, Constructor c, int index | + exists(ConstructorCall cc, int index | cc.getConstructedType() instanceof TypeUnboundIdSearchRequest | n1.asExpr() = cc.getArgument(index) and n2.asExpr() = cc and - c = cc.getConstructor() and - not isVarargs(c, index) + not isVarargs(cc.getConstructor(), index) ) } @@ -398,12 +253,9 @@ predicate unboundIdSearchRequestStep(ExprNode n1, ExprNode n2) { * and UnboundID `SearchRequest`, i.e. `taintedSearchRequest.duplicate()`. */ predicate unboundIdSearchRequestDuplicateStep(ExprNode n1, ExprNode n2) { - exists(MethodAccess ma, Method m | - n1.asExpr() = ma.getQualifier() and - n2.asExpr() = ma - | + exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma | ma.getMethod() = m and - m.getDeclaringType().getAnAncestor() instanceof TypeReadOnlySearchRequest and + m.getDeclaringType().getAnAncestor() instanceof TypeUnboundIdReadOnlySearchRequest and m.hasName("duplicate") ) } @@ -415,11 +267,11 @@ predicate unboundIdSearchRequestDuplicateStep(ExprNode n1, ExprNode n2) { predicate unboundIdSearchRequestSetStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m | n1.asExpr() = ma.getAnArgument() and - n2.asExpr() = ma.getQualifier() + n2.asExpr() = ma.getQualifier() and + ma.getMethod() = m | - ma.getMethod() = m and - m.getDeclaringType() instanceof TypeUnboundIdSearchRequest and - (m.hasName("setBaseDN") or m.hasName("setFilter")) + m instanceof MethodUnboundIdSearchRequestSetBaseDN or + m instanceof MethodUnboundIdSearchRequestSetFilter ) } @@ -429,13 +281,13 @@ predicate unboundIdSearchRequestSetStep(ExprNode n1, ExprNode n2) { */ predicate ldapQueryStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m, int index | - n1.asExpr() = ma.getArgument(index) - | + n1.asExpr() = ma.getArgument(index) and n2.asExpr() = ma and ma.getMethod() = m and - m.getDeclaringType() instanceof TypeLdapQueryBuilder and - (m.hasName("filter") or m.hasName("base")) and index = 0 + | + m instanceof MethodSpringLdapQueryBuilderFilter or + m instanceof MethodSpringLdapQueryBuilderBase ) } @@ -446,11 +298,10 @@ predicate ldapQueryStep(ExprNode n1, ExprNode n2) { predicate ldapQueryBaseStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and - n2.asExpr() = ma + n2.asExpr() = ma and + ma.getMethod() = m | - ma.getMethod() = m and - m.getDeclaringType() instanceof TypeLdapQueryBuilder and - m.hasName("base") and + m instanceof MethodSpringLdapQueryBuilderBase and m.getNumberOfParameters() = 0 ) } @@ -463,18 +314,18 @@ predicate ldapQueryBaseStep(ExprNode n1, ExprNode n2) { predicate ldapQueryBuilderStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and - n2.asExpr() = ma + n2.asExpr() = ma and + ma.getMethod() = m | - ma.getMethod() = m and ( - m.getDeclaringType() instanceof TypeLdapQueryBuilder or - m.getDeclaringType() instanceof TypeConditionCriteria or - m.getDeclaringType() instanceof TypeContainerCriteria + m.getDeclaringType() instanceof TypeSpringLdapQueryBuilder or + m.getDeclaringType() instanceof TypeSpringConditionCriteria or + m.getDeclaringType() instanceof TypeSpringContainerCriteria ) and ( - m.getReturnType() instanceof TypeLdapQueryBuilder or - m.getReturnType() instanceof TypeConditionCriteria or - m.getReturnType() instanceof TypeContainerCriteria + m.getReturnType() instanceof TypeSpringLdapQueryBuilder or + m.getReturnType() instanceof TypeSpringConditionCriteria or + m.getReturnType() instanceof TypeSpringContainerCriteria ) ) } @@ -484,7 +335,7 @@ predicate ldapQueryBuilderStep(ExprNode n1, ExprNode n2) { * `HardcodedFilter`, i.e. `new HardcodedFilter(tainted)`. */ predicate hardcodedFilterStep(ExprNode n1, ExprNode n2) { - exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeHardcodedFilter | + exists(ConstructorCall cc | cc.getConstructedType() instanceof TypeSpringHardcodedFilter | n1.asExpr() = cc.getAnArgument() and n2.asExpr() = cc ) @@ -498,9 +349,9 @@ predicate hardcodedFilterStep(ExprNode n1, ExprNode n2) { predicate springLdapFilterToStringStep(ExprNode n1, ExprNode n2) { exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and - (n2.asExpr() = ma or n2.asExpr() = ma.getAnArgument()) + (n2.asExpr() = ma or n2.asExpr() = ma.getAnArgument()) and + ma.getMethod() = m | - ma.getMethod() = m and m.getDeclaringType().getAnAncestor() instanceof TypeSpringLdapFilter and (m.hasName("encode") or m.hasName("toString")) ) @@ -512,12 +363,14 @@ predicate springLdapFilterToStringStep(ExprNode n1, ExprNode n2) { * `LdapNameBuilder.newInstance().add(tainted)`. */ predicate ldapNameBuilderStep(ExprNode n1, ExprNode n2) { - exists(MethodAccess ma, Method m | n1.asExpr() = ma.getAnArgument() | + exists(MethodAccess ma, Method m | + n1.asExpr() = ma.getAnArgument() and (n2.asExpr() = ma or n2.asExpr() = ma.getQualifier()) and ma.getMethod() = m and - m.getDeclaringType() instanceof TypeLdapNameBuilder and - (m.hasName("newInstance") or m.hasName("add")) and m.getNumberOfParameters() = 1 + | + m instanceof MethodSpringLdapNameBuilderNewInstance or + m instanceof MethodSpringLdapNameBuilderAdd ) } @@ -526,11 +379,8 @@ predicate ldapNameBuilderStep(ExprNode n1, ExprNode n2) { * and `LdapName`, `LdapNameBuilder.build()`. */ predicate ldapNameBuilderBuildStep(ExprNode n1, ExprNode n2) { - exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() | - n2.asExpr() = ma and - ma.getMethod() = m and - m.getDeclaringType() instanceof TypeLdapNameBuilder and - m.hasName("build") + exists(MethodAccess ma | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma | + ma.getMethod() instanceof MethodSpringLdapNameBuilderBuild ) } @@ -539,11 +389,8 @@ predicate ldapNameBuilderBuildStep(ExprNode n1, ExprNode n2) { * Spring `LdapUtils.newLdapName`, i.e. `LdapUtils.newLdapName(tainted)`. */ predicate ldapUtilsStep(ExprNode n1, ExprNode n2) { - exists(MethodAccess ma, Method m | n1.asExpr() = ma.getAnArgument() | - n2.asExpr() = ma and - ma.getMethod() = m and - m.getDeclaringType() instanceof TypeLdapUtils and - m.hasName("newLdapName") + exists(MethodAccess ma | n1.asExpr() = ma.getAnArgument() and n2.asExpr() = ma | + ma.getMethod() instanceof MethodSpringLdapUtilsNewLdapName ) } @@ -567,10 +414,7 @@ predicate apacheSearchRequestStep(ExprNode n1, ExprNode n2) { * and filter or DN i.e. `tainterSearchRequest.getFilter()` or `taintedSearchRequest.getBase()`. */ predicate apacheSearchRequestGetStep(ExprNode n1, ExprNode n2) { - exists(MethodAccess ma, Method m | - n1.asExpr() = ma.getQualifier() and - n2.asExpr() = ma - | + exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma | ma.getMethod() = m and m.getDeclaringType().getAnAncestor() instanceof TypeApacheSearchRequest and (m.hasName("getFilter") or m.hasName("getBase")) @@ -593,10 +437,7 @@ predicate apacheLdapDnStep(ExprNode n1, ExprNode n2) { * and `String` i.e. `taintedDn.getName()`, `taintedDn.getNormName()` or `taintedDn.toString()`. */ predicate apacheLdapDnGetStep(ExprNode n1, ExprNode n2) { - exists(MethodAccess ma, Method m | - n1.asExpr() = ma.getQualifier() and - n2.asExpr() = ma - | + exists(MethodAccess ma, Method m | n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma | ma.getMethod() = m and m.getDeclaringType().getAnAncestor() instanceof TypeApacheDn and (m.hasName("getName") or m.hasName("getNormName") or m.hasName("toString")) diff --git a/java/ql/src/semmle/code/java/frameworks/ApacheLdap.qll b/java/ql/src/semmle/code/java/frameworks/ApacheLdap.qll new file mode 100644 index 00000000000..0ad904a7b46 --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/ApacheLdap.qll @@ -0,0 +1,29 @@ +/** + * Provides classes and predicates for working with the Apache LDAP API. + */ + +import java +import semmle.code.java.Type +import semmle.code.java.Member + +/*--- Types ---*/ +/** The interface `org.apache.directory.ldap.client.api.LdapConnection`. */ +class TypeApacheLdapConnection extends Interface { + TypeApacheLdapConnection() { + this.hasQualifiedName("org.apache.directory.ldap.client.api", "LdapConnection") + } +} + +/** The interface `org.apache.directory.api.ldap.model.message.SearchRequest`. */ +class TypeApacheSearchRequest extends Interface { + TypeApacheSearchRequest() { + this.hasQualifiedName("org.apache.directory.api.ldap.model.message", "SearchRequest") + } +} + +/** The class `org.apache.directory.api.ldap.model.name.Dn`. */ +class TypeApacheDn extends Class { + TypeApacheDn() { + this.hasQualifiedName("org.apache.directory.api.ldap.model.name", "Dn") + } +} \ No newline at end of file diff --git a/java/ql/src/semmle/code/java/frameworks/Jndi.qll b/java/ql/src/semmle/code/java/frameworks/Jndi.qll new file mode 100644 index 00000000000..18aea029ddb --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/Jndi.qll @@ -0,0 +1,59 @@ +/** + * Provides classes and predicates for working with the Java JDBC API. + */ + +import java +import semmle.code.java.Type +import semmle.code.java.Member + +/*--- Types ---*/ +/** The interface `javax.naming.directory.DirContext`. */ +class TypeDirContext extends Interface { + TypeDirContext() { this.hasQualifiedName("javax.naming.directory", "DirContext") } +} + +/** The class `javax.naming.ldap.LdapName`. */ +class TypeLdapName extends Class { + TypeLdapName() { this.hasQualifiedName("javax.naming.ldap", "LdapName") } +} + +/*--- Methods ---*/ +/** A method with the name `addAll` declared in `javax.naming.ldap.LdapName`. */ +class MethodLdapNameAddAll extends Method { + MethodLdapNameAddAll() { + getDeclaringType() instanceof TypeLdapName and + hasName("addAll") + } +} + +/** A method with the name `clone` declared in `javax.naming.ldap.LdapName`. */ +class MethodLdapNameClone extends Method { + MethodLdapNameClone() { + getDeclaringType() instanceof TypeLdapName and + hasName("clone") + } +} + +/** A method with the name `getAll` declared in `javax.naming.ldap.LdapName`. */ +class MethodLdapNameGetAll extends Method { + MethodLdapNameGetAll() { + getDeclaringType() instanceof TypeLdapName and + hasName("getAll") + } +} + +/** A method with the name `getRdns` declared in `javax.naming.ldap.LdapName`. */ +class MethodLdapNameGetRdns extends Method { + MethodLdapNameGetRdns() { + getDeclaringType() instanceof TypeLdapName and + hasName("getRdns") + } +} + +/** A method with the name `toString` declared in `javax.naming.ldap.LdapName`. */ +class MethodLdapNameToString extends Method { + MethodLdapNameToString() { + getDeclaringType() instanceof TypeLdapName and + hasName("toString") + } +} \ No newline at end of file diff --git a/java/ql/src/semmle/code/java/frameworks/SpringLdap.qll b/java/ql/src/semmle/code/java/frameworks/SpringLdap.qll new file mode 100644 index 00000000000..babe029276f --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/SpringLdap.qll @@ -0,0 +1,191 @@ +/** + * Provides classes and predicates for working with the Spring LDAP API. + */ + +import java +import semmle.code.java.Type +import semmle.code.java.Member + +/*--- Types ---*/ +/** The class `org.springframework.ldap.core.LdapTemplate`. */ +class TypeSpringLdapTemplate extends Class { + TypeSpringLdapTemplate() { this.hasQualifiedName("org.springframework.ldap.core", "LdapTemplate") } +} + +/** The class `org.springframework.ldap.query.LdapQueryBuilder`. */ +class TypeSpringLdapQueryBuilder extends Class { + TypeSpringLdapQueryBuilder() { + this.hasQualifiedName("org.springframework.ldap.query", "LdapQueryBuilder") + } +} + +/** The interface `org.springframework.ldap.query.ConditionCriteria`. */ +class TypeSpringConditionCriteria extends Interface { + TypeSpringConditionCriteria() { + this.hasQualifiedName("org.springframework.ldap.query", "ConditionCriteria") + } +} + +/** The interface `org.springframework.ldap.query.ContainerCriteria`. */ +class TypeSpringContainerCriteria extends Interface { + TypeSpringContainerCriteria() { + this.hasQualifiedName("org.springframework.ldap.query", "ContainerCriteria") + } +} + +/** The class `org.springframework.ldap.filter.HardcodedFilter`. */ +class TypeSpringHardcodedFilter extends Class { + TypeSpringHardcodedFilter() { + this.hasQualifiedName("org.springframework.ldap.filter", "HardcodedFilter") + } +} + +/** The interface `org.springframework.ldap.filter.Filter`. */ +class TypeSpringLdapFilter extends Interface { + TypeSpringLdapFilter() { this.hasQualifiedName("org.springframework.ldap.filter", "Filter") } +} + +/** The class `org.springframework.ldap.support.LdapNameBuilder`. */ +class TypeSpringLdapNameBuilder extends Class { + TypeSpringLdapNameBuilder() { + this.hasQualifiedName("org.springframework.ldap.support", "LdapNameBuilder") + } +} + +/** The class `org.springframework.ldap.support.LdapUtils`. */ +class TypeSpringLdapUtils extends Class { + TypeSpringLdapUtils() { this.hasQualifiedName("org.springframework.ldap.support", "LdapUtils") } +} + +/*--- Methods ---*/ +/** + * A method with the name `authenticate` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateAuthenticate extends Method { + MethodSpringLdapTemplateAuthenticate() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("authenticate") + } +} + +/** + * A method with the name `find` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateFind extends Method { + MethodSpringLdapTemplateFind() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("find") + } +} + +/** + * A method with the name `findOne` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateFindOne extends Method { + MethodSpringLdapTemplateFindOne() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("findOne") + } +} + +/** + * A method with the name `search` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateSearch extends Method { + MethodSpringLdapTemplateSearch() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("search") + } +} + +/** + * A method with the name `searchForContext` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateSearchForContext extends Method { + MethodSpringLdapTemplateSearchForContext() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("searchForContext") + } +} + +/** + * A method with the name `searchForObject` declared in + * `org.springframework.ldap.core.LdapTemplate`. + */ +class MethodSpringLdapTemplateSearchForObject extends Method { + MethodSpringLdapTemplateSearchForObject() { + getDeclaringType() instanceof TypeSpringLdapTemplate and + hasName("searchForObject") + } +} + +/** + * A method with the name `filter` declared in + * `org.springframework.ldap.query.LdapQueryBuilder`. + */ +class MethodSpringLdapQueryBuilderFilter extends Method { + MethodSpringLdapQueryBuilderFilter() { + getDeclaringType() instanceof TypeSpringLdapQueryBuilder and + hasName("filter") + } +} + +/** + * A method with the name `base` declared in + * `org.springframework.ldap.query.LdapQueryBuilder`. + */ +class MethodSpringLdapQueryBuilderBase extends Method { + MethodSpringLdapQueryBuilderBase() { + getDeclaringType() instanceof TypeSpringLdapQueryBuilder and + hasName("base") + } +} + +/** + * A method with the name `newInstance` declared in + * `org.springframework.ldap.support.LdapNameBuilder`. + */ +class MethodSpringLdapNameBuilderNewInstance extends Method { + MethodSpringLdapNameBuilderNewInstance() { + getDeclaringType() instanceof TypeSpringLdapNameBuilder and + hasName("newInstance") + } +} + +/** + * A method with the name `add` declared in + * `org.springframework.ldap.support.LdapNameBuilder`. + */ +class MethodSpringLdapNameBuilderAdd extends Method { + MethodSpringLdapNameBuilderAdd() { + getDeclaringType() instanceof TypeSpringLdapNameBuilder and + hasName("add") + } +} + +/** + * A method with the name `build` declared in + * `org.springframework.ldap.support.LdapNameBuilder`. + */ +class MethodSpringLdapNameBuilderBuild extends Method { + MethodSpringLdapNameBuilderBuild() { + getDeclaringType() instanceof TypeSpringLdapNameBuilder and + hasName("build") + } +} + +/** + * A method with the name `newLdapName` declared in + * `org.springframework.ldap.support.LdapUtils`. + */ +class MethodSpringLdapUtilsNewLdapName extends Method { + MethodSpringLdapUtilsNewLdapName() { + getDeclaringType() instanceof TypeSpringLdapUtils and + hasName("newLdapName") + } +} \ No newline at end of file diff --git a/java/ql/src/semmle/code/java/frameworks/UnboundId.qll b/java/ql/src/semmle/code/java/frameworks/UnboundId.qll new file mode 100644 index 00000000000..7c54c946f1b --- /dev/null +++ b/java/ql/src/semmle/code/java/frameworks/UnboundId.qll @@ -0,0 +1,113 @@ +/** + * Provides classes and predicates for working with the UnboundID API. + */ + +import java +import semmle.code.java.Type +import semmle.code.java.Member + +/*--- Types ---*/ +/** The interface `com.unboundid.ldap.sdk.ReadOnlySearchRequest`. */ +class TypeUnboundIdReadOnlySearchRequest extends Interface { + TypeUnboundIdReadOnlySearchRequest() { + this.hasQualifiedName("com.unboundid.ldap.sdk", "ReadOnlySearchRequest") + } +} + +/** The class `com.unboundid.ldap.sdk.SearchRequest`. */ +class TypeUnboundIdSearchRequest extends Class { + TypeUnboundIdSearchRequest() { this.hasQualifiedName("com.unboundid.ldap.sdk", "SearchRequest") } +} + +/** The class `com.unboundid.ldap.sdk.Filter`. */ +class TypeUnboundIdLdapFilter extends Class { + TypeUnboundIdLdapFilter() { this.hasQualifiedName("com.unboundid.ldap.sdk", "Filter") } +} + +/** The class `com.unboundid.ldap.sdk.LDAPConnection`. */ +class TypeUnboundIdLDAPConnection extends Class { + TypeUnboundIdLDAPConnection() { + this.hasQualifiedName("com.unboundid.ldap.sdk", "LDAPConnection") + } +} + +/*--- Methods ---*/ +/** A method with the name `setBaseDN` declared in `com.unboundid.ldap.sdk.SearchRequest`. */ +class MethodUnboundIdSearchRequestSetBaseDN extends Method { + MethodUnboundIdSearchRequestSetBaseDN() { + getDeclaringType() instanceof TypeUnboundIdSearchRequest and + hasName("setBaseDN") + } +} + +/** A method with the name `setFilter` declared in `com.unboundid.ldap.sdk.SearchRequest`. */ +class MethodUnboundIdSearchRequestSetFilter extends Method { + MethodUnboundIdSearchRequestSetFilter() { + getDeclaringType() instanceof TypeUnboundIdSearchRequest and + hasName("setFilter") + } +} + +/** A method with the name `create` declared in `com.unboundid.ldap.sdk.Filter`. */ +class MethodUnboundIdFilterCreate extends Method { + MethodUnboundIdFilterCreate() { + getDeclaringType() instanceof TypeUnboundIdLdapFilter and + hasName("create") + } +} + +/** A method with the name `createANDFilter` declared in `com.unboundid.ldap.sdk.Filter`. */ +class MethodUnboundIdFilterCreateANDFilter extends Method { + MethodUnboundIdFilterCreateANDFilter() { + getDeclaringType() instanceof TypeUnboundIdLdapFilter and + hasName("createANDFilter") + } +} + +/** A method with the name `createORFilter` declared in `com.unboundid.ldap.sdk.Filter`. */ +class MethodUnboundIdFilterCreateORFilter extends Method { + MethodUnboundIdFilterCreateORFilter() { + getDeclaringType() instanceof TypeUnboundIdLdapFilter and + hasName("createORFilter") + } +} + +/** A method with the name `createNOTFilter` declared in `com.unboundid.ldap.sdk.Filter`. */ +class MethodUnboundIdFilterCreateNOTFilter extends Method { + MethodUnboundIdFilterCreateNOTFilter() { + getDeclaringType() instanceof TypeUnboundIdLdapFilter and + hasName("createNOTFilter") + } +} + +/** A method with the name `simplifyFilter` declared in `com.unboundid.ldap.sdk.Filter`. */ +class MethodUnboundIdFilterSimplifyFilter extends Method { + MethodUnboundIdFilterSimplifyFilter() { + getDeclaringType() instanceof TypeUnboundIdLdapFilter and + hasName("simplifyFilter") + } +} + +/** A method with the name `search` declared in `com.unboundid.ldap.sdk.LDAPConnection`. */ +class MethodUnboundIdLDAPConnectionSearch extends Method { + MethodUnboundIdLDAPConnectionSearch() { + getDeclaringType() instanceof TypeUnboundIdLDAPConnection and + hasName("search") + } +} + +/** A method with the name `asyncSearch` declared in `com.unboundid.ldap.sdk.LDAPConnection`. */ +class MethodUnboundIdLDAPConnectionAsyncSearch extends Method { + MethodUnboundIdLDAPConnectionAsyncSearch() { + getDeclaringType() instanceof TypeUnboundIdLDAPConnection and + hasName("asyncSearch") + } +} + +/** A method with the name `searchForEntry` declared in `com.unboundid.ldap.sdk.LDAPConnection`. */ +class MethodUnboundIdLDAPConnectionSearchForEntry extends Method { + MethodUnboundIdLDAPConnectionSearchForEntry() { + getDeclaringType() instanceof TypeUnboundIdLDAPConnection and + hasName("searchForEntry") + } +} \ No newline at end of file From 95723b08e137c435f8209fbf6798f985243f7399 Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Sat, 18 Jan 2020 19:01:35 +0100 Subject: [PATCH 018/148] Query to detect LDAP injections in Java Add help --- .../Security/CWE/CWE-90/LdapInjection.qhelp | 62 +++++++++++++++++++ .../CWE/CWE-90/LdapInjectionApache.java | 22 +++++++ .../CWE/CWE-90/LdapInjectionJndi.java | 34 ++++++++++ .../CWE/CWE-90/LdapInjectionSpring.java | 17 +++++ .../CWE/CWE-90/LdapInjectionUnboundId.java | 17 +++++ 5 files changed, 152 insertions(+) create mode 100644 java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp create mode 100644 java/ql/src/Security/CWE/CWE-90/LdapInjectionApache.java create mode 100644 java/ql/src/Security/CWE/CWE-90/LdapInjectionJndi.java create mode 100644 java/ql/src/Security/CWE/CWE-90/LdapInjectionSpring.java create mode 100644 java/ql/src/Security/CWE/CWE-90/LdapInjectionUnboundId.java diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp b/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp new file mode 100644 index 00000000000..9cc666a0366 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp @@ -0,0 +1,62 @@ + + + +

If an LDAP query is built using string concatenation, and the +components of the concatenation include user input, a user +is likely to be able to run malicious LDAP queries.

+
+ + +

If user input must be included in an LDAP query, it should be escaped to +avoid a malicious user providing special characters that change the meaning +of the query. If possible build the LDAP query (or search filter / DN) using your +framework helper methods to avoid string concatenation, or escape user input +using the right LDAP encoding method, for example encodeForLDAP from OWASP ESAPI, +LdapEncoder from Spring LDAP or Filter.encodeValue from UnboundID library.

+
+ + +

In the following examples, the code accepts an "organization name" and a "username" +from the user, which it uses to query LDAP.

+ +

The first example concatenates the unvalidated and unencoded user input directly +into both the DN (Distinguished Name) and the search filter used for the LDAP query. +A malicious user could provide special characters to change the meaning of these +queries, and search for a completely different set of values. The LDAP query is executed +using Java JNDI API. +

+ +

The second example uses the OWASP ESAPI library to encode the user values +before they are included in the DN and search filters. This ensures the meaning of +the query cannot be changed by a malicious user.

+ + + +

The third example uses Spring LdapQueryBuilder to build LDAP query. In addition to +simplifying building of complex search parameters, it also provides proper escaping of any +unsafe characters in search filters. DN is built using LdapNameBuilder, which also provides +proper escaping.

+ + + +

The fourth example uses UnboundID Filter and DN classes to construct safe filter and +base DN.

+ + + +

The fifth example shows how to build safe filter and DN using Apache LDAP API.

+ + +
+ + +
  • OWASP: LDAP Injection Prevention Cheat Sheet.
  • +
  • OWASP: Preventing LDAP Injection in Java.
  • +
  • OWASP ESAPI: OWASP ESAPI.
  • +
  • Spring LdapQueryBuilder doc: LdapQueryBuilder.
  • +
  • Spring LdapNameBuilder doc: LdapNameBuilder.
  • +
  • UnboundID: Understanding and Defending Against LDAP Injection Attacks.
  • +
    +
    diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionApache.java b/java/ql/src/Security/CWE/CWE-90/LdapInjectionApache.java new file mode 100644 index 00000000000..5a7b2be5e3f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjectionApache.java @@ -0,0 +1,22 @@ +import org.apache.directory.ldap.client.api.LdapConnection; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.api.ldap.model.name.Rdn; +import org.apache.directory.api.ldap.model.message.SearchRequest; +import org.apache.directory.api.ldap.model.message.SearchRequestImpl; +import static org.apache.directory.ldap.client.api.search.FilterBuilder.equal; + +public void ldapQueryGood(HttpServletRequest request, LdapConnection c) { + String organizationName = request.getParameter("organization_name"); + String username = request.getParameter("username"); + + // GOOD: Organization name is encoded before being used in DN + Dn safeDn = new Dn(new Rdn("OU", "People"), new Rdn("O", organizationName)); + + // GOOD: User input is encoded before being used in search filter + String safeFilter = equal("username", username); + + SearchRequest searchRequest = new SearchRequestImpl(); + searchRequest.setBase(safeDn); + searchRequest.setFilter(safeFilter); + c.search(searchRequest); +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionJndi.java b/java/ql/src/Security/CWE/CWE-90/LdapInjectionJndi.java new file mode 100644 index 00000000000..6bcb481c51f --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjectionJndi.java @@ -0,0 +1,34 @@ +import javax.naming.directory.DirContext; +import org.owasp.esapi.Encoder; +import org.owasp.esapi.reference.DefaultEncoder; + +public void ldapQueryBad(HttpServletRequest request, DirContext ctx) throws NamingException { + String organizationName = request.getParameter("organization_name"); + String username = request.getParameter("username"); + + // BAD: User input used in DN (Distinguished Name) without encoding + String dn = "OU=People,O=" + organizationName; + + // BAD: User input used in search filter without encoding + String filter = "username=" + userName; + + ctx.search(dn, filter, new SearchControls()); +} + +public void ldapQueryGood(HttpServletRequest request, DirContext ctx) throws NamingException { + String organizationName = request.getParameter("organization_name"); + String username = request.getParameter("username"); + + // ESAPI encoder + Encoder encoder = DefaultEncoder.getInstance(); + + // GOOD: Organization name is encoded before being used in DN + String safeOrganizationName = encoder.encodeForDN(organizationName); + String safeDn = "OU=People,O=" + safeOrganizationName; + + // GOOD: User input is encoded before being used in search filter + String safeUsername = encoder.encodeForLDAP(username); + String safeFilter = "username=" + safeUsername; + + ctx.search(safeDn, safeFilter, new SearchControls()); +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionSpring.java b/java/ql/src/Security/CWE/CWE-90/LdapInjectionSpring.java new file mode 100644 index 00000000000..e3265ace995 --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjectionSpring.java @@ -0,0 +1,17 @@ +import static org.springframework.ldap.query.LdapQueryBuilder.query; +import org.springframework.ldap.support.LdapNameBuilder; + +public void ldapQueryGood(@RequestParam String organizationName, @RequestParam String username) { + // GOOD: Organization name is encoded before being used in DN + String safeDn = LdapNameBuilder.newInstance() + .add("O", organizationName) + .add("OU=People") + .build().toString(); + + // GOOD: User input is encoded before being used in search filter + LdapQuery query = query() + .base(safeDn) + .where("username").is(username); + + ldapTemplate.search(query, new AttributeCheckAttributesMapper()); +} \ No newline at end of file diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionUnboundId.java b/java/ql/src/Security/CWE/CWE-90/LdapInjectionUnboundId.java new file mode 100644 index 00000000000..fac4cbfc0ef --- /dev/null +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjectionUnboundId.java @@ -0,0 +1,17 @@ +import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.DN; +import com.unboundid.ldap.sdk.RDN; +import com.unboundid.ldap.sdk.Filter; + +public void ldapQueryGood(HttpServletRequest request, LDAPConnection c) { + String organizationName = request.getParameter("organization_name"); + String username = request.getParameter("username"); + + // GOOD: Organization name is encoded before being used in DN + DN safeDn = new DN(new RDN("OU", "People"), new RDN("O", organizationName)); + + // GOOD: User input is encoded before being used in search filter + Filter safeFilter = Filter.createEqualityFilter("username", username); + + c.search(safeDn.toString(), SearchScope.ONE, safeFilter); +} \ No newline at end of file From 00ee3d2549623735d39ec61cbf15abea3adda417 Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Sat, 18 Jan 2020 20:21:38 +0100 Subject: [PATCH 019/148] Query to detect LDAP injections in Java Cleanup --- .../Security/CWE/CWE-90/LdapInjectionLib.qll | 24 ++++++++----------- .../code/java/frameworks/ApacheLdap.qll | 6 ++--- .../src/semmle/code/java/frameworks/Jndi.qll | 2 +- .../code/java/frameworks/SpringLdap.qll | 6 +++-- .../semmle/code/java/frameworks/UnboundId.qll | 2 +- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll index acda7e9aad1..42b65019d9c 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll @@ -6,7 +6,6 @@ import semmle.code.java.frameworks.UnboundId import semmle.code.java.frameworks.SpringLdap import semmle.code.java.frameworks.ApacheLdap - /** Holds if the parameter of `c` at index `paramIndex` is varargs. */ bindingset[paramIndex] predicate isVarargs(Callable c, int paramIndex) { @@ -20,8 +19,8 @@ abstract class LdapInjectionSource extends DataFlow::Node { } abstract class LdapInjectionSink extends DataFlow::ExprNode { } /** -* A taint-tracking configuration for unvalidated user input that is used to construct LDAP queries. -*/ + * A taint-tracking configuration for unvalidated user input that is used to construct LDAP queries. + */ class LdapInjectionFlowConfig extends TaintTracking::Configuration { LdapInjectionFlowConfig() { this = "LdapInjectionFlowConfig" } @@ -79,7 +78,7 @@ class JndiLdapInjectionSink extends LdapInjectionSink { | m.getDeclaringType().getAnAncestor() instanceof TypeDirContext and m.hasName("search") and - index in [0..1] + index in [0 .. 1] ) } } @@ -129,16 +128,13 @@ class SpringLdapInjectionSink extends LdapInjectionSink { ) and ( // Parameter index is 1 (DN or query) or 2 (filter) if method is not authenticate - ( - index in [0..1] and - not m instanceof MethodSpringLdapTemplateAuthenticate - ) or + index in [0 .. 1] and + not m instanceof MethodSpringLdapTemplateAuthenticate + or // But it's not the last parameter in case of authenticate method (last param is password) - ( - index in [0..1] and - index < m.getNumberOfParameters() - 1 and - m instanceof MethodSpringLdapTemplateAuthenticate - ) + index in [0 .. 1] and + index < m.getNumberOfParameters() - 1 and + m instanceof MethodSpringLdapTemplateAuthenticate ) ) } @@ -442,4 +438,4 @@ predicate apacheLdapDnGetStep(ExprNode n1, ExprNode n2) { m.getDeclaringType().getAnAncestor() instanceof TypeApacheDn and (m.hasName("getName") or m.hasName("getNormName") or m.hasName("toString")) ) -} \ No newline at end of file +} diff --git a/java/ql/src/semmle/code/java/frameworks/ApacheLdap.qll b/java/ql/src/semmle/code/java/frameworks/ApacheLdap.qll index 0ad904a7b46..a1cf6376bdf 100644 --- a/java/ql/src/semmle/code/java/frameworks/ApacheLdap.qll +++ b/java/ql/src/semmle/code/java/frameworks/ApacheLdap.qll @@ -23,7 +23,5 @@ class TypeApacheSearchRequest extends Interface { /** The class `org.apache.directory.api.ldap.model.name.Dn`. */ class TypeApacheDn extends Class { - TypeApacheDn() { - this.hasQualifiedName("org.apache.directory.api.ldap.model.name", "Dn") - } -} \ No newline at end of file + TypeApacheDn() { this.hasQualifiedName("org.apache.directory.api.ldap.model.name", "Dn") } +} diff --git a/java/ql/src/semmle/code/java/frameworks/Jndi.qll b/java/ql/src/semmle/code/java/frameworks/Jndi.qll index 18aea029ddb..f25f8908033 100644 --- a/java/ql/src/semmle/code/java/frameworks/Jndi.qll +++ b/java/ql/src/semmle/code/java/frameworks/Jndi.qll @@ -56,4 +56,4 @@ class MethodLdapNameToString extends Method { getDeclaringType() instanceof TypeLdapName and hasName("toString") } -} \ No newline at end of file +} diff --git a/java/ql/src/semmle/code/java/frameworks/SpringLdap.qll b/java/ql/src/semmle/code/java/frameworks/SpringLdap.qll index babe029276f..f9dc9e81c84 100644 --- a/java/ql/src/semmle/code/java/frameworks/SpringLdap.qll +++ b/java/ql/src/semmle/code/java/frameworks/SpringLdap.qll @@ -9,7 +9,9 @@ import semmle.code.java.Member /*--- Types ---*/ /** The class `org.springframework.ldap.core.LdapTemplate`. */ class TypeSpringLdapTemplate extends Class { - TypeSpringLdapTemplate() { this.hasQualifiedName("org.springframework.ldap.core", "LdapTemplate") } + TypeSpringLdapTemplate() { + this.hasQualifiedName("org.springframework.ldap.core", "LdapTemplate") + } } /** The class `org.springframework.ldap.query.LdapQueryBuilder`. */ @@ -188,4 +190,4 @@ class MethodSpringLdapUtilsNewLdapName extends Method { getDeclaringType() instanceof TypeSpringLdapUtils and hasName("newLdapName") } -} \ No newline at end of file +} diff --git a/java/ql/src/semmle/code/java/frameworks/UnboundId.qll b/java/ql/src/semmle/code/java/frameworks/UnboundId.qll index 7c54c946f1b..8eee0f14ce5 100644 --- a/java/ql/src/semmle/code/java/frameworks/UnboundId.qll +++ b/java/ql/src/semmle/code/java/frameworks/UnboundId.qll @@ -110,4 +110,4 @@ class MethodUnboundIdLDAPConnectionSearchForEntry extends Method { getDeclaringType() instanceof TypeUnboundIdLDAPConnection and hasName("searchForEntry") } -} \ No newline at end of file +} From ffbd0f6632002e180900b1871863bb012cf7031a Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 20 Jan 2020 09:56:40 +0100 Subject: [PATCH 020/148] update expected test output --- .../InterProceduralFlow/DataFlow.expected | 4 -- .../InterProceduralFlow/GermanFlow.expected | 4 -- .../InterProceduralFlow/TrackedNodes.expected | 20 +++++++ .../Promises/AdditionalPromises.expected | 57 ++++++++++++++----- .../ql/test/library-tests/Promises/flow.js | 10 ++++ .../library-tests/Promises/tests.expected | 7 +++ .../Security/CWE-079/ReflectedXss.expected | 6 -- 7 files changed, 79 insertions(+), 29 deletions(-) diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected index 24e5f967f9f..5b5ac277940 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected @@ -22,12 +22,8 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | -| promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | -| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | -| promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | -| promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | | properties.js:2:16:2:24 | "tainted" | properties.js:5:14:5:23 | a.someProp | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected index 9c3980797ea..c10ab912bf2 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected @@ -23,12 +23,8 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | -| promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | -| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | -| promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | -| promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | | properties.js:2:16:2:24 | "tainted" | properties.js:5:14:5:23 | a.someProp | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected b/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected index e69de29bb2d..9aa7cc76049 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected @@ -0,0 +1,20 @@ +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:20:7:20:7 | v | +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:21:20:21:20 | v | +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:23:19:23:19 | v | +| missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:24:20:24:20 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:20:7:20:7 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:23:19:23:19 | v | +| missing | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:20:7:20:7 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:21:20:21:20 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:23:19:23:19 | v | +| missing | promises.js:13:9:13:21 | exceptional return of Math.random() | promises.js:24:20:24:20 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:20:7:20:7 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:21:20:21:20 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:23:19:23:19 | v | +| missing | promises.js:14:7:14:21 | exceptional return of res(res_source) | promises.js:24:20:24:20 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:20:7:20:7 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:21:20:21:20 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:23:19:23:19 | v | +| missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:24:20:24:20 | v | diff --git a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index 7e2e94c3454..0159e921700 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -1,19 +1,46 @@ | additional-promises.js:2:13:2:57 | new Pin ... ct) {}) | -| flowsteps.js:5:3:5:21 | new Promise(throws) | -| flowsteps.js:8:3:8:21 | new Promise(throws) | -| flowsteps.js:12:11:12:29 | new Promise(throws) | -| flowsteps.js:17:3:17:45 | new Pro ... ect(3)) | -| flowsteps.js:21:11:21:53 | new Pro ... ect(4)) | -| flowsteps.js:26:3:26:21 | new Promise(throws) | -| flowsteps.js:26:3:27:19 | new Pro ... => {}) | -| flowsteps.js:31:3:31:21 | new Promise(throws) | -| flowsteps.js:31:3:32:19 | new Pro ... => {}) | -| flowsteps.js:31:3:33:35 | new Pro ... og(e6)) | -| flowsteps.js:36:19:36:62 | new Pro ... lve(8)) | -| flowsteps.js:38:19:38:62 | new Pro ... lve(9)) | -| flowsteps.js:40:11:40:28 | Promise.resolve(3) | -| flowsteps.js:43:12:49:4 | new Pro ... }\\n }) | -| flowsteps.js:50:19:50:41 | p2.then ... al * 4) | +| flow.js:7:11:7:59 | new Pro ... ource)) | +| flow.js:10:11:10:58 | new Pro ... ource)) | +| flow.js:13:11:13:58 | new Pro ... ource)) | +| flow.js:20:2:20:24 | Promise ... source) | +| flow.js:22:2:22:24 | Promise ... source) | +| flow.js:24:2:24:49 | new Pro ... ource)) | +| flow.js:26:2:26:49 | new Pro ... ource)) | +| flow.js:28:2:28:23 | Promise ... ("foo") | +| flow.js:28:2:28:41 | Promise ... source) | +| flow.js:30:2:30:24 | Promise ... source) | +| flow.js:30:2:30:41 | Promise ... "foo") | +| flow.js:32:2:32:49 | new Pro ... ource)) | +| flow.js:34:2:34:24 | Promise ... source) | +| flow.js:34:2:34:41 | Promise ... => { }) | +| flow.js:36:11:36:33 | Promise ... source) | +| flow.js:37:11:37:29 | p5.catch(() => { }) | +| flow.js:40:2:40:49 | new Pro ... ource)) | +| flow.js:40:2:40:65 | new Pro ... => { }) | +| flow.js:42:2:42:49 | new Pro ... ource)) | +| flow.js:42:2:42:76 | new Pro ... => { }) | +| flow.js:44:2:44:24 | Promise ... source) | +| flow.js:44:2:44:41 | Promise ... => { }) | +| flow.js:44:2:44:58 | Promise ... => { }) | +| flow.js:44:2:44:75 | Promise ... => { }) | +| flow.js:46:2:46:24 | Promise ... source) | +| flow.js:46:2:46:43 | Promise ... => { }) | +| flow.js:48:2:48:36 | new Pro ... urce }) | +| flow.js:53:2:53:22 | createP ... source) | +| flow.js:55:11:55:58 | new Pro ... ource)) | +| flow.js:56:11:56:27 | p8.then(() => {}) | +| flow.js:57:12:57:31 | p9.finally(() => {}) | +| flow.js:60:12:60:59 | new Pro ... ource)) | +| flow.js:61:12:61:29 | p11.then(() => {}) | +| flow.js:65:9:65:56 | new Pro ... ource)) | +| flow.js:74:10:74:57 | new Pro ... ource)) | +| flow.js:76:2:76:17 | chainedPromise() | +| flow.js:76:2:76:32 | chained ... => {}) | +| flow.js:86:23:86:70 | new Pro ... ource)) | +| interflow.js:6:3:6:25 | loadScr ... urce()) | +| interflow.js:6:3:7:26 | loadScr ... () { }) | +| interflow.js:6:3:8:26 | loadScr ... () { }) | +| interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | | promises.js:33:19:35:6 | new Pro ... \\n }) | diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js index 1ef55b3a548..84b11753c27 100644 --- a/javascript/ql/test/library-tests/Promises/flow.js +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -74,4 +74,14 @@ return new Promise((resolve, reject) => reject(source)).then(() => {}); } chainedPromise().then(() => {}).catch(e => sink(e)); // NOT OK! + + function leaksResolvedPromise(p) { + p.then(x => sink(x)); // NOT OK! + } + leaksResolvedPromise(Promise.resolve(source)); + + function leaksRejectedPromise(p) { + p.catch(e => sink(e)); // NOT OK! + } + leaksRejectedPromise(new Promise((resolve, reject) => reject(source))); })(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index cc452dbe62d..1f2d9f04929 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -9,6 +9,7 @@ test_ResolvedPromiseDefinition | flow.js:44:2:44:24 | Promise ... source) | flow.js:44:18:44:23 | source | | flow.js:46:2:46:24 | Promise ... source) | flow.js:46:18:46:23 | source | | flow.js:51:10:51:29 | Promise.resolve(src) | flow.js:51:26:51:28 | src | +| flow.js:81:23:81:45 | Promise ... source) | flow.js:81:39:81:44 | source | | promises.js:53:19:53:41 | Promise ... source) | promises.js:53:35:53:40 | source | | promises.js:62:19:62:41 | Promise ... source) | promises.js:62:35:62:40 | source | | promises.js:71:5:71:27 | Promise ... source) | promises.js:71:21:71:26 | source | @@ -34,6 +35,7 @@ test_PromiseDefinition_getExecutor | flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:24:60:58 | (resolv ... source) | | flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:21:65:55 | (resolv ... source) | | flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:22:74:56 | (resolv ... source) | +| flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:35:86:69 | (resolv ... source) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | @@ -55,6 +57,7 @@ test_PromiseDefinition | flow.js:60:12:60:59 | new Pro ... ource)) | | flow.js:65:9:65:56 | new Pro ... ource)) | | flow.js:74:10:74:57 | new Pro ... ource)) | +| flow.js:86:23:86:70 | new Pro ... ource)) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | @@ -86,6 +89,7 @@ test_PromiseDefinition_getRejectParameter | flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:34:60:39 | reject | | flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:31:65:36 | reject | | flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:32:74:37 | reject | +| flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:45:86:50 | reject | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | @@ -104,6 +108,7 @@ test_PromiseDefinition_getResolveParameter | flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:60:25:60:31 | resolve | | flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:22:65:28 | resolve | | flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:23:74:29 | resolve | +| flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:36:86:42 | resolve | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | @@ -132,4 +137,6 @@ flow | flow.js:2:15:2:22 | "source" | flow.js:62:22:62:22 | x | | flow.js:2:15:2:22 | "source" | flow.js:70:8:70:8 | e | | flow.js:2:15:2:22 | "source" | flow.js:76:50:76:50 | e | +| flow.js:2:15:2:22 | "source" | flow.js:79:20:79:20 | x | +| flow.js:2:15:2:22 | "source" | flow.js:84:21:84:21 | e | | interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected index bcf54b75580..b5b4b7aec1e 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected @@ -46,8 +46,6 @@ nodes | promises.js:5:44:5:57 | req.query.data | | promises.js:5:44:5:57 | req.query.data | | promises.js:6:11:6:11 | x | -| promises.js:6:11:6:11 | x | -| promises.js:6:25:6:25 | x | | promises.js:6:25:6:25 | x | | promises.js:6:25:6:25 | x | | tst2.js:6:7:6:30 | p | @@ -108,10 +106,6 @@ edges | promises.js:5:3:5:59 | new Pro ... .data)) | promises.js:6:11:6:11 | x | | promises.js:5:44:5:57 | req.query.data | promises.js:5:3:5:59 | new Pro ... .data)) | | promises.js:5:44:5:57 | req.query.data | promises.js:5:3:5:59 | new Pro ... .data)) | -| promises.js:5:44:5:57 | req.query.data | promises.js:6:11:6:11 | x | -| promises.js:5:44:5:57 | req.query.data | promises.js:6:11:6:11 | x | -| promises.js:6:11:6:11 | x | promises.js:6:25:6:25 | x | -| promises.js:6:11:6:11 | x | promises.js:6:25:6:25 | x | | promises.js:6:11:6:11 | x | promises.js:6:25:6:25 | x | | promises.js:6:11:6:11 | x | promises.js:6:25:6:25 | x | | tst2.js:6:7:6:30 | p | tst2.js:7:12:7:12 | p | From ad813ef86c26268f0e4043bedc6a83f098163ef1 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 20 Jan 2020 14:16:29 +0100 Subject: [PATCH 021/148] add flowsTo to the use of isAdditionalLoadStep --- .../javascript/dataflow/Configuration.qll | 33 ++++--------------- .../ql/test/library-tests/Promises/flow.js | 15 +++++++++ .../library-tests/Promises/tests.expected | 11 +++++++ 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index 24aef92d6f4..f25f5b0b861 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -788,12 +788,14 @@ private predicate parameterPropRead( Function f, DataFlow::Node invk, DataFlow::Node arg, string prop, DataFlow::Node read, DataFlow::Configuration cfg ) { - exists(DataFlow::Node parm | + exists(DataFlow::SourceNode parm | callInputStep(f, invk, arg, parm, cfg) and ( - read = parm.(DataFlow::SourceNode).getAPropertyRead(prop) + read = parm.getAPropertyRead(prop) or - isAdditionalLoadStep(parm, read, prop, cfg) + exists(DataFlow::Node use | parm.flowsTo(use) | + isAdditionalLoadStep(use, read, prop, cfg) + ) ) ) } @@ -881,7 +883,7 @@ private predicate reachableFromStoreBase( ( flowStep(mid, cfg, nd, newSummary) or - existsCopyProperty(mid, nd, prop, cfg) and + isAdditionalCopyPropertyStep(mid, nd, prop, cfg) and newSummary = PathSummary::level() ) and summary = oldSummary.appendValuePreserving(newSummary) @@ -906,29 +908,6 @@ private predicate flowThroughProperty( ) } -/** - * Holds if the property `prop` is copied from `fromNode` to `toNode`. - */ -bindingset[prop, cfg] -private predicate existsCopyProperty(DataFlow::Node fromNode, DataFlow::Node toNode, string prop, DataFlow::Configuration cfg) { - fromNode = toNode - or - existsCopyPropertyRecursive(fromNode, toNode, prop, cfg) -} - -/** - * Holds if the property `prop` is copied from `fromNode` to `toNode` using at least 1 step. - * - * The recursion of this predicate has been unfolded once compared to a naive implementation in order to avoid having no constraint on `prop`. - * Therefore a caller of this predicate should also test whether the `toNode` and `fromNode` are equal. - */ -private predicate existsCopyPropertyRecursive(DataFlow::Node fromNode, DataFlow::Node toNode, string prop, DataFlow::Configuration cfg) { - exists(DataFlow::Node mid | - isAdditionalCopyPropertyStep(fromNode, mid, prop, cfg) and - existsCopyProperty(mid, toNode, prop, cfg) - ) -} - /** * Holds if `arg` and `cb` are passed as arguments to a function which in turn * invokes `cb`, passing `arg` as its `i`th argument. diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js index 84b11753c27..990c5e2bcfd 100644 --- a/javascript/ql/test/library-tests/Promises/flow.js +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -84,4 +84,19 @@ p.catch(e => sink(e)); // NOT OK! } leaksRejectedPromise(new Promise((resolve, reject) => reject(source))); + + function leaksRejectedAgain(p) { + ("foo", p).then(() => {}).catch(e => sink(e)); // NOT OK! + } + leaksRejectedAgain(new Promise((resolve, reject) => reject(source)).then(() => {})); + + async function returnsRejected(p) { + try { + await p; + } catch(e) { + return e; + } + } + var foo = returnsRejected(new Promise((resolve, reject) => reject(source))); + sink(foo); // NOT OK! })(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 1f2d9f04929..e9f147840ac 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -36,6 +36,8 @@ test_PromiseDefinition_getExecutor | flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:21:65:55 | (resolv ... source) | | flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:22:74:56 | (resolv ... source) | | flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:35:86:69 | (resolv ... source) | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:33:91:67 | (resolv ... source) | +| flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:40:100:74 | (resolv ... source) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | @@ -58,6 +60,8 @@ test_PromiseDefinition | flow.js:65:9:65:56 | new Pro ... ource)) | | flow.js:74:10:74:57 | new Pro ... ource)) | | flow.js:86:23:86:70 | new Pro ... ource)) | +| flow.js:91:21:91:68 | new Pro ... ource)) | +| flow.js:100:28:100:75 | new Pro ... ource)) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | @@ -71,6 +75,7 @@ test_PromiseDefinition_getAResolveHandler | flow.js:55:11:55:58 | new Pro ... ource)) | flow.js:56:19:56:26 | () => {} | | flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:61:21:61:28 | () => {} | | flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:64:74:71 | () => {} | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:75:91:82 | () => {} | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | @@ -90,6 +95,8 @@ test_PromiseDefinition_getRejectParameter | flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:31:65:36 | reject | | flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:32:74:37 | reject | | flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:45:86:50 | reject | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:43:91:48 | reject | +| flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:50:100:55 | reject | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | @@ -109,6 +116,8 @@ test_PromiseDefinition_getResolveParameter | flow.js:65:9:65:56 | new Pro ... ource)) | flow.js:65:22:65:28 | resolve | | flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:23:74:29 | resolve | | flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:36:86:42 | resolve | +| flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:34:91:40 | resolve | +| flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:41:100:47 | resolve | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | @@ -139,4 +148,6 @@ flow | flow.js:2:15:2:22 | "source" | flow.js:76:50:76:50 | e | | flow.js:2:15:2:22 | "source" | flow.js:79:20:79:20 | x | | flow.js:2:15:2:22 | "source" | flow.js:84:21:84:21 | e | +| flow.js:2:15:2:22 | "source" | flow.js:89:45:89:45 | e | +| flow.js:2:15:2:22 | "source" | flow.js:101:7:101:9 | foo | | interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | From 5c6134db99602ced98a87c63535f73d961403708 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 20 Jan 2020 14:55:49 +0100 Subject: [PATCH 022/148] a bit of self-review and an auto-format --- .../ql/src/semmle/javascript/Promises.qll | 2 +- .../javascript/dataflow/Configuration.qll | 183 +++++++++--------- 2 files changed, 95 insertions(+), 90 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 9259f6f472f..bc9309e31e2 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -215,7 +215,7 @@ private module ExceptionalPromiseFlow { override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and - pred = getCallback([0..1]).getExceptionalReturn() and + pred = getCallback(0).getExceptionalReturn() and succ = this } } diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index f25f5b0b861..5c484651b40 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -225,19 +225,21 @@ abstract class Configuration extends string { } /** - * Holds if the `pred` should be stored in the object `succ` under the property `prop`. + * Holds if `pred` should be stored in the object `succ` under the property `prop`. */ predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } /** - * Holds if the property `prop` of the object `pred` should be loaded into `succ`. + * Holds if the property `prop` of the object `pred` should be loaded into `succ`. */ predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } /** * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. */ - predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + none() + } } /** @@ -333,21 +335,20 @@ abstract class BarrierGuardNode extends DataFlow::Node { outcome = cond.getOutcome() and barrierGuardBlocksAccessPath(this, outcome, p, label) and cond.dominates(bb) - ) + ) } - + /** - * Holds if there exists an input variable of `ref` that blocks the label `label`. - * - * This predicate is outlined to give the optimizer a hint about the join ordering. + * Holds if there exists an input variable of `ref` that blocks the label `label`. + * + * This predicate is outlined to give the optimizer a hint about the join ordering. */ private predicate ssaRefinementBlocks(boolean outcome, SsaRefinementNode ref, string label) { - getEnclosingExpr() = ref.getGuard().getTest() and + getEnclosingExpr() = ref.getGuard().getTest() and forex(SsaVariable input | input = ref.getAnInput() | barrierGuardBlocksExpr(this, outcome, input.getAUse(), label) ) } - /** * Holds if this node blocks expression `e` provided it evaluates to `outcome`. @@ -363,11 +364,13 @@ abstract class BarrierGuardNode extends DataFlow::Node { } /** - * Holds if data flow node `nd` acts as a barrier for data flow. - * - * `label` is bound to the blocked label, or the empty string if all labels should be blocked. - */ -private predicate barrierGuardBlocksExpr(BarrierGuardNode guard, boolean outcome, Expr test, string label) { + * Holds if data flow node `nd` acts as a barrier for data flow. + * + * `label` is bound to the blocked label, or the empty string if all labels should be blocked. + */ +private predicate barrierGuardBlocksExpr( + BarrierGuardNode guard, boolean outcome, Expr test, string label +) { guard.blocks(outcome, test) and label = "" or guard.blocks(outcome, test, label) @@ -378,23 +381,29 @@ private predicate barrierGuardBlocksExpr(BarrierGuardNode guard, boolean outcome } /** - * Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through - * an access path. - * - * `label` is bound to the blocked label, or the empty string if all labels should be blocked. - */ + * Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through + * an access path. + * + * `label` is bound to the blocked label, or the empty string if all labels should be blocked. + */ pragma[noinline] -private predicate barrierGuardBlocksAccessPath(BarrierGuardNode guard, boolean outcome, AccessPath ap, string label) { +private predicate barrierGuardBlocksAccessPath( + BarrierGuardNode guard, boolean outcome, AccessPath ap, string label +) { barrierGuardBlocksExpr(guard, outcome, ap.getAnInstance(), label) } /** - * Holds if `guard` should block flow along the edge `pred -> succ`. - * - * `label` is bound to the blocked label, or the empty string if all labels should be blocked. - */ -private predicate barrierGuardBlocksEdge(BarrierGuardNode guard, DataFlow::Node pred, DataFlow::Node succ, string label) { - exists(SsaVariable input, SsaPhiNode phi, BasicBlock bb, ConditionGuardNode cond, boolean outcome | + * Holds if `guard` should block flow along the edge `pred -> succ`. + * + * `label` is bound to the blocked label, or the empty string if all labels should be blocked. + */ +private predicate barrierGuardBlocksEdge( + BarrierGuardNode guard, DataFlow::Node pred, DataFlow::Node succ, string label +) { + exists( + SsaVariable input, SsaPhiNode phi, BasicBlock bb, ConditionGuardNode cond, boolean outcome + | pred = DataFlow::ssaDefinitionNode(input) and succ = DataFlow::ssaDefinitionNode(phi) and input = phi.getInputFromBlock(bb) and @@ -424,7 +433,9 @@ private predicate isBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow * Holds if there is a labeled barrier edge `pred -> succ` in `cfg` either through an explicit barrier edge * or one implied by a barrier guard. */ -private predicate isLabeledBarrierEdge(Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label) { +private predicate isLabeledBarrierEdge( + Configuration cfg, DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel label +) { cfg.isBarrierEdge(pred, succ, label) or exists(DataFlow::BarrierGuardNode guard | @@ -476,7 +487,7 @@ abstract class AdditionalFlowStep extends DataFlow::Node { } /** - * Holds if the `pred` should be stored in the object `succ` under the property `prop`. + * Holds if `pred` should be stored in the object `succ` under the property `prop`. */ cached predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } @@ -594,10 +605,9 @@ private predicate exploratoryFlowStep( basicFlowStep(pred, succ, _, cfg) or basicStoreStep(pred, succ, _) or basicLoadStep(pred, succ, _) or - isAdditionalStoreStep(pred, succ, _, cfg) or isAdditionalLoadStep(pred, succ, _, cfg) or - isAdditionalCopyPropertyStep(pred, succ, _ ,cfg) or + isAdditionalCopyPropertyStep(pred, succ, _, cfg) or // the following two disjuncts taken together over-approximate flow through // higher-order calls callback(pred, succ) or @@ -769,10 +779,8 @@ private predicate storeStep( ( returnedPropWrite(f, _, prop, mid) or - exists(DataFlow::SourceNode base | + exists(DataFlow::SourceNode base | base.flowsToExpr(f.getAReturnedExpr()) | isAdditionalStoreStep(mid, base, prop, cfg) - and - base.flowsToExpr(f.getAReturnedExpr()) ) or succ instanceof DataFlow::NewNode and @@ -793,9 +801,7 @@ private predicate parameterPropRead( ( read = parm.getAPropertyRead(prop) or - exists(DataFlow::Node use | parm.flowsTo(use) | - isAdditionalLoadStep(use, read, prop, cfg) - ) + exists(DataFlow::Node use | parm.flowsTo(use) | isAdditionalLoadStep(use, read, prop, cfg)) ) ) } @@ -821,16 +827,20 @@ private predicate reachesReturn( /** * Holds if the property `prop` of the object `pred` should be loaded into `succ`. */ -private predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { +private predicate isAdditionalLoadStep( + DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg +) { any(AdditionalFlowStep s).load(pred, succ, prop) or cfg.isAdditionalLoadStep(pred, succ, prop) } /** - * Holds if the `pred` should be stored in the object `succ` under the property `prop`. + * Holds if `pred` should be stored in the object `succ` under the property `prop`. */ -private predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { +private predicate isAdditionalStoreStep( + DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg +) { any(AdditionalFlowStep s).store(pred, succ, prop) or cfg.isAdditionalStoreStep(pred, succ, prop) @@ -839,7 +849,9 @@ private predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ /** * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. */ -private predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg) { +private predicate isAdditionalCopyPropertyStep( + DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg +) { any(AdditionalFlowStep s).copyProperty(pred, succ, prop) or cfg.isAdditionalCopyPropertyStep(pred, succ, prop) @@ -881,7 +893,7 @@ private predicate reachableFromStoreBase( exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary | reachableFromStoreBase(prop, rhs, mid, cfg, oldSummary) and ( - flowStep(mid, cfg, nd, newSummary) + flowStep(mid, cfg, nd, newSummary) or isAdditionalCopyPropertyStep(mid, nd, prop, cfg) and newSummary = PathSummary::level() @@ -1093,19 +1105,19 @@ private predicate onPath(DataFlow::Node nd, DataFlow::Configuration cfg, PathSum * Holds if there is a configuration that has at least one source and at least one sink. */ pragma[noinline] -private predicate isLive() { exists(DataFlow::Configuration cfg | isSource(_, cfg, _) and isSink(_, cfg, _)) } +private predicate isLive() { + exists(DataFlow::Configuration cfg | isSource(_, cfg, _) and isSink(_, cfg, _)) +} /** * A data flow node on an inter-procedural path from a source. */ private newtype TPathNode = - MkSourceNode(DataFlow::Node nd, DataFlow::Configuration cfg) { isSourceNode(nd, cfg, _) } - or + MkSourceNode(DataFlow::Node nd, DataFlow::Configuration cfg) { isSourceNode(nd, cfg, _) } or MkMidNode(DataFlow::Node nd, DataFlow::Configuration cfg, PathSummary summary) { isLive() and onPath(nd, cfg, summary) - } - or + } or MkSinkNode(DataFlow::Node nd, DataFlow::Configuration cfg) { isSinkNode(nd, cfg, _) } /** @@ -1166,9 +1178,7 @@ class PathNode extends TPathNode { } /** Holds if this path node wraps data-flow node `nd` and configuration `c`. */ - predicate wraps(DataFlow::Node n, DataFlow::Configuration c) { - nd = n and cfg = c - } + predicate wraps(DataFlow::Node n, DataFlow::Configuration c) { nd = n and cfg = c } /** Gets the underlying configuration of this path node. */ DataFlow::Configuration getConfiguration() { result = cfg } @@ -1177,9 +1187,7 @@ class PathNode extends TPathNode { DataFlow::Node getNode() { result = nd } /** Gets a successor node of this path node. */ - final PathNode getASuccessor() { - result = getASuccessor(this) - } + final PathNode getASuccessor() { result = getASuccessor(this) } /** Gets a textual representation of this path node. */ string toString() { result = nd.toString() } @@ -1223,7 +1231,10 @@ private MidPathNode finalMidNode(SinkPathNode snk) { * This helper predicate exists to clarify the intended join order in `getASuccessor` below. */ pragma[noinline] -private predicate midNodeStep(PathNode nd, DataFlow::Node predNd, Configuration cfg, PathSummary summary, DataFlow::Node succNd, PathSummary newSummary) { +private predicate midNodeStep( + PathNode nd, DataFlow::Node predNd, Configuration cfg, PathSummary summary, DataFlow::Node succNd, + PathSummary newSummary +) { nd = MkMidNode(predNd, cfg, summary) and flowStep(predNd, id(cfg), succNd, newSummary) } @@ -1236,7 +1247,10 @@ private PathNode getASuccessor(PathNode nd) { result = initialMidNode(nd) or // mid node to mid node - exists(Configuration cfg, DataFlow::Node predNd, PathSummary summary, DataFlow::Node succNd, PathSummary newSummary | + exists( + Configuration cfg, DataFlow::Node predNd, PathSummary summary, DataFlow::Node succNd, + PathSummary newSummary + | midNodeStep(nd, predNd, cfg, summary, succNd, newSummary) and result = MkMidNode(succNd, id(cfg), summary.append(newSummary)) ) @@ -1307,9 +1321,7 @@ class SinkPathNode extends PathNode, MkSinkNode { */ module PathGraph { /** Holds if `nd` is a node in the graph of data flow path explanations. */ - query predicate nodes(PathNode nd) { - not nd.(MidPathNode).isHidden() - } + query predicate nodes(PathNode nd) { not nd.(MidPathNode).isHidden() } /** * Gets a node to which data from `nd` may flow in one step, skipping over hidden nodes. @@ -1317,7 +1329,8 @@ module PathGraph { private PathNode succ0(PathNode nd) { result = getASuccessorIfHidden*(nd.getASuccessor()) and // skip hidden nodes - nodes(nd) and nodes(result) + nodes(nd) and + nodes(result) } /** @@ -1357,27 +1370,21 @@ module PathGraph { } } - +/** + * Gets an operand of the given `&&` operator. + * + * We use this to construct the transitive closure over a relation + * that does not include all of `BinaryExpr.getAnOperand`. + */ +private Expr getALogicalAndOperand(LogAndExpr e) { result = e.getAnOperand() } /** - * Gets an operand of the given `&&` operator. - * - * We use this to construct the transitive closure over a relation - * that does not include all of `BinaryExpr.getAnOperand`. - */ -private Expr getALogicalAndOperand(LogAndExpr e) { - result = e.getAnOperand() -} - -/** - * Gets an operand of the given `||` operator. - * - * We use this to construct the transitive closure over a relation - * that does not include all of `BinaryExpr.getAnOperand`. - */ -private Expr getALogicalOrOperand(LogOrExpr e) { - result = e.getAnOperand() -} + * Gets an operand of the given `||` operator. + * + * We use this to construct the transitive closure over a relation + * that does not include all of `BinaryExpr.getAnOperand`. + */ +private Expr getALogicalOrOperand(LogOrExpr e) { result = e.getAnOperand() } /** * A `BarrierGuardNode` that controls which data flow @@ -1392,8 +1399,8 @@ abstract class AdditionalBarrierGuardNode extends BarrierGuardNode { } /** - * A function that returns the result of a barrier guard. - */ + * A function that returns the result of a barrier guard. + */ private class BarrierGuardFunction extends Function { DataFlow::ParameterNode sanitizedParameter; BarrierGuardNode guard; @@ -1426,8 +1433,8 @@ private class BarrierGuardFunction extends Function { } /** - * Holds if this function sanitizes argument `e` of call `call`, provided the call evaluates to `outcome`. - */ + * Holds if this function sanitizes argument `e` of call `call`, provided the call evaluates to `outcome`. + */ predicate isBarrierCall(DataFlow::CallNode call, Expr e, boolean outcome, string lbl) { exists(DataFlow::Node arg | arg.asExpr() = e and @@ -1440,22 +1447,20 @@ private class BarrierGuardFunction extends Function { } /** - * Holds if this function applies to the flow in `cfg`. - */ + * Holds if this function applies to the flow in `cfg`. + */ predicate appliesTo(Configuration cfg) { cfg.isBarrierGuard(guard) } } /** - * A call that sanitizes an argument. - */ + * A call that sanitizes an argument. + */ private class AdditionalBarrierGuardCall extends AdditionalBarrierGuardNode, DataFlow::CallNode { BarrierGuardFunction f; AdditionalBarrierGuardCall() { f.isBarrierCall(this, _, _, _) } - override predicate blocks(boolean outcome, Expr e) { - f.isBarrierCall(this, e, outcome, "") - } + override predicate blocks(boolean outcome, Expr e) { f.isBarrierCall(this, e, outcome, "") } predicate internalBlocksLabel(boolean outcome, Expr e, DataFlow::FlowLabel label) { f.isBarrierCall(this, e, outcome, label) From 649464912513b4c6d4d4e9a4e8d26717eb447627 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 20 Dec 2019 15:38:13 +0100 Subject: [PATCH 023/148] fix a number of FPs in js/exception-xss --- .../security/dataflow/DomBasedXss.qll | 9 ---- .../security/dataflow/ExceptionXss.qll | 45 ++++++++++++++----- .../javascript/security/dataflow/Xss.qll | 20 +++++++++ .../test/query-tests/Security/CWE-079/tst.js | 7 +++ 4 files changed, 62 insertions(+), 19 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll index 24634eec94c..deca4bd3db4 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXss.qll @@ -21,15 +21,6 @@ module DomBasedXss { override predicate isSanitizer(DataFlow::Node node) { super.isSanitizer(node) or - exists(PropAccess pacc | pacc = node.asExpr() | - isSafeLocationProperty(pacc) - or - // `$(location.hash)` is a fairly common and safe idiom - // (because `location.hash` always starts with `#`), - // so we mark `hash` as safe for the purposes of this query - pacc.getPropertyName() = "hash" - ) - or node instanceof Sanitizer } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll index c74b48f1e2e..1d4dabe8a8a 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll @@ -12,15 +12,39 @@ module ExceptionXss { import Xss as Xss private import semmle.javascript.dataflow.InferredTypes + /** + * Gets the name of a method that does not leak taint from its arguments if an exception is thrown by the method. + */ + private string getAnUnlikelyToThrowMethodName() { + result = "getElementById" or + result = "indexOf" or + result = "stringify" or + result = "assign" or + // fs methods. (The callback argument to the async functions are vulnerable, but its unlikely that the callback is the user-controlled part). + result = "existsSync" or + result = "exists" or + result = "writeFileSync" or + result = "writeFile" or + result = "appendFile" or + result = "appendFileSync" or + result = "pick" or + // log.info etc. + result = "info" or + result = "warn" or + result = "error" or + result = "join" or + result = "val" or // $.val + result = "parse" or // JSON.parse + result = "push" or // Array.prototype.push + result = "test" // RegExp.prototype.test + } + /** * Holds if `node` is unlikely to cause an exception containing sensitive information to be thrown. */ private predicate isUnlikelyToThrowSensitiveInformation(DataFlow::Node node) { - node = any(DataFlow::CallNode call | call.getCalleeName() = "getElementById").getAnArgument() - or - node = any(DataFlow::CallNode call | call.getCalleeName() = "indexOf").getAnArgument() - or - node = any(DataFlow::CallNode call | call.getCalleeName() = "stringify").getAnArgument() + node = any(DataFlow::CallNode call | call.getCalleeName() = getAnUnlikelyToThrowMethodName()) + .getAnArgument() or node = DataFlow::globalVarRef("console").getAMemberCall(_).getAnArgument() } @@ -38,6 +62,7 @@ module ExceptionXss { */ predicate canThrowSensitiveInformation(DataFlow::Node node) { not isUnlikelyToThrowSensitiveInformation(node) and + not node instanceof Xss::Shared::Sink and // removes duplicates from js/xss. ( // in the case of reflective calls the below ensures that both InvokeNodes have no known callee. forex(DataFlow::InvokeNode call | call.getAnArgument() = node | not exists(call.getACallee())) @@ -79,15 +104,15 @@ module ExceptionXss { } /** - * Get the parameter in the callback that contains an error. + * Get the parameter in the callback that contains an error. * In the current implementation this is always the first parameter. */ DataFlow::Node getErrorParam() { result = errorParameter } } /** - * Gets the error parameter for a callback that is supplied to the same call as `pred` is an argument to. - * For example: `outerCall(foo, , bar, (, val) => { ... })`. + * Gets the error parameter for a callback that is supplied to the same call as `pred` is an argument to. + * For example: `outerCall(foo, , bar, (, val) => { ... })`. */ DataFlow::Node getCallbackErrorParam(DataFlow::Node pred) { exists(DataFlow::CallNode call, Callback callback | @@ -101,8 +126,8 @@ module ExceptionXss { /** * Gets the data-flow node to which any exceptions thrown by * this expression will propagate. - * This predicate adds, on top of `Expr::getExceptionTarget`, exceptions - * propagated by callbacks. + * This predicate adds, on top of `Expr::getExceptionTarget`, exceptions + * propagated by callbacks. */ private DataFlow::Node getExceptionTarget(DataFlow::Node pred) { result = pred.asExpr().getExceptionTarget() diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll index 71b2a463f7a..6ff78fe24dd 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll @@ -51,6 +51,24 @@ module Shared { ) } } + + /** + * A property read from a safe property is considered a sanitizer. + */ + class SafePropertyReadSanitizer extends Sanitizer, DataFlow::Node { + SafePropertyReadSanitizer() { + exists(PropAccess pacc | pacc = this.asExpr() | + isSafeLocationProperty(pacc) + or + // `$(location.hash)` is a fairly common and safe idiom + // (because `location.hash` always starts with `#`), + // so we mark `hash` as safe for the purposes of this query + pacc.getPropertyName() = "hash" + or + pacc.getPropertyName() = "length" + ) + } + } } /** Provides classes and predicates for the DOM-based XSS query. */ @@ -269,6 +287,8 @@ module DomBasedXss { private class MetacharEscapeSanitizer extends Sanitizer, Shared::MetacharEscapeSanitizer { } private class UriEncodingSanitizer extends Sanitizer, Shared::UriEncodingSanitizer { } + + private class SafePropertyReadSanitizer extends Sanitizer, Shared::SafePropertyReadSanitizer {} } /** Provides classes and predicates for the reflected XSS query. */ diff --git a/javascript/ql/test/query-tests/Security/CWE-079/tst.js b/javascript/ql/test/query-tests/Security/CWE-079/tst.js index 9a0d34b277a..1ec5610ff83 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/tst.js +++ b/javascript/ql/test/query-tests/Security/CWE-079/tst.js @@ -318,3 +318,10 @@ function basicExceptions() { function handlebarsSafeString() { return new Handlebars.SafeString(location); // NOT OK! } + +function test2() { + var target = document.location.search + + // OK + $('myId').html(target.length) +} \ No newline at end of file From 6648e2751f9af8f63714204e68cf4b5c5b47084c Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 21 Jan 2020 15:49:42 +0100 Subject: [PATCH 024/148] remove use of .getAlocalSource() i custom load/store test --- .../ql/test/library-tests/CustomLoadStoreSteps/test.ql | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql index 7b27d81e18a..e4ffc89294d 100644 --- a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql @@ -13,7 +13,11 @@ class Configuration extends TaintTracking::Configuration { // When the source code states that "foo" is being read, "bar" is additionally being read. override predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { - pred.(DataFlow::SourceNode).getAPropertyRead("foo") = succ and prop = "bar" + exists(DataFlow::PropRead read | read = succ | + read.getBase() = pred and + read.getPropertyName() = "foo" + ) and + prop = "bar" } } From d8b25ef5a25a21bda235d33162b949e930807b14 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 21 Jan 2020 15:52:50 +0100 Subject: [PATCH 025/148] add data-flow steps for resolved promises using pseudo-properties --- .../ql/src/semmle/javascript/Promises.qll | 74 +++++++++++++++++-- .../ql/test/library-tests/Promises/flow.js | 4 + .../library-tests/Promises/tests.expected | 15 ++++ 3 files changed, 87 insertions(+), 6 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index bc9309e31e2..f5404b21678 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -121,9 +121,17 @@ class AggregateES2015PromiseDefinition extends PromiseCreationCall { } /** - * This module defines how exceptional data-flow propagates into and out a Promise. + * This module defines how data-flow propagates into and out of a Promise. + * The data-flow is based on pseudo-properties rather than tainting the Promise object (which is what `PromiseTaintStep` does). */ -private module ExceptionalPromiseFlow { +private module PromiseFlow { + /** + * Gets the pseudo-field used to describe resolved values in a promise. + */ + string resolveField() { + result = "$PromiseResolveField$" + } + /** * Gets the pseudo-field used to describe rejected values in a promise. */ @@ -134,7 +142,7 @@ private module ExceptionalPromiseFlow { /** * A flow step describing a promise definition. * - * The rejected value is written to a pseudo-field on the promise. + * The resolved/rejected value is written to a pseudo-field on the promise. */ class PromiseDefitionStep extends DataFlow::AdditionalFlowStep { PromiseDefinition promise; @@ -143,6 +151,10 @@ private module ExceptionalPromiseFlow { } override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = promise.getResolveParameter().getACall().getArgument(0) and + succ = this + or prop = rejectField() and ( pred = promise.getRejectParameter().getACall().getArgument(0) or @@ -152,6 +164,23 @@ private module ExceptionalPromiseFlow { } } + /** + * A flow step describing the a Promise.resolve (and similar) call. + */ + class CreationStep extends DataFlow::AdditionalFlowStep { + PromiseCreationCall promise; + CreationStep() { + this = promise + } + + override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = promise.getValue() and + succ = this + } + } + + /** * A load step loading the pseudo-field describing that the promise is rejected. * The rejected value is thrown as a exception. @@ -165,6 +194,10 @@ private module ExceptionalPromiseFlow { } override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + succ = this and + pred = operand + or prop = rejectField() and succ = await.getExceptionTarget() and pred = operand @@ -180,6 +213,10 @@ private module ExceptionalPromiseFlow { } override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getReceiver() and + succ = getCallback(0).getParameter(0) + or prop = rejectField() and pred = getReceiver() and succ = getCallback(1).getParameter(0) @@ -193,12 +230,16 @@ private module ExceptionalPromiseFlow { } override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getCallback([0..1]).getAReturn() and + succ = this + or prop = rejectField() and pred = getCallback([0..1]).getExceptionalReturn() and succ = this } } - + /** * A flow step describing the data-flow related to the `.catch` method of a promise. */ @@ -213,10 +254,20 @@ private module ExceptionalPromiseFlow { succ = getCallback(0).getParameter(0) } + override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = resolveField() and + pred = getReceiver().getALocalSource() and + succ = this + } + override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and pred = getCallback(0).getExceptionalReturn() and succ = this + or + prop = resolveField() and + pred = getCallback(0).getAReturn() and + succ = this } } @@ -229,10 +280,17 @@ private module ExceptionalPromiseFlow { } override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { - prop = rejectField() and + (prop = resolveField() or prop = rejectField()) and pred = getReceiver() and succ = this } + + // a similar thing can also happen if a rejected promise is returned. + override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + prop = rejectField() and + pred = getCallback(0).getExceptionalReturn() and + succ = this + } } } @@ -258,10 +316,14 @@ predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) { succ = thn ) or - // from `p` to `p.catch(..)` exists(DataFlow::MethodCallNode catch | catch.getMethodName() = "catch" | + // from `p` to `p.catch(..)` pred = catch.getReceiver() and succ = catch + or + // from `v` to `p.catch(x => return v)` + pred = catch.getCallback(0).getAReturn() and + succ = catch ) or // from `p` to `p.finally(..)` diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js index 990c5e2bcfd..51e29398ed9 100644 --- a/javascript/ql/test/library-tests/Promises/flow.js +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -99,4 +99,8 @@ } var foo = returnsRejected(new Promise((resolve, reject) => reject(source))); sink(foo); // NOT OK! + + new Promise((resolve, reject) => reject("BLA")).catch(x => {return source}).then(x => sink(x)); // NOT OK + + new Promise((resolve, reject) => reject("BLA")).finally(x => {throw source}).catch(x => sink(x)); // NOT OK })(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index e9f147840ac..211d3ea3091 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -18,6 +18,8 @@ test_PromiseDefinition_getARejectHandler | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | | flow.js:42:2:42:49 | new Pro ... ource)) | flow.js:42:67:42:75 | () => { } | | flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:56:103:75 | x => {return source} | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:20:6:22:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | @@ -38,12 +40,15 @@ test_PromiseDefinition_getExecutor | flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:35:86:69 | (resolv ... source) | | flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:33:91:67 | (resolv ... source) | | flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:40:100:74 | (resolv ... source) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:14:103:47 | (resolv ... ("BLA") | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:14:105:47 | (resolv ... ("BLA") | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | | promises.js:33:19:35:6 | new Pro ... \\n }) | promises.js:33:31:35:5 | functio ... ;\\n } | | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:29:45:5 | functio ... ;\\n } | test_PromiseDefinition_getAFinallyHandler +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | test_PromiseDefinition | flow.js:7:11:7:59 | new Pro ... ource)) | @@ -62,6 +67,8 @@ test_PromiseDefinition | flow.js:86:23:86:70 | new Pro ... ource)) | | flow.js:91:21:91:68 | new Pro ... ource)) | | flow.js:100:28:100:75 | new Pro ... ource)) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | @@ -76,6 +83,7 @@ test_PromiseDefinition_getAResolveHandler | flow.js:60:12:60:59 | new Pro ... ource)) | flow.js:61:21:61:28 | () => {} | | flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:64:74:71 | () => {} | | flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:75:91:82 | () => {} | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | @@ -97,6 +105,8 @@ test_PromiseDefinition_getRejectParameter | flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:45:86:50 | reject | | flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:43:91:48 | reject | | flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:50:100:55 | reject | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:24:103:29 | reject | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:24:105:29 | reject | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | @@ -118,6 +128,8 @@ test_PromiseDefinition_getResolveParameter | flow.js:86:23:86:70 | new Pro ... ource)) | flow.js:86:36:86:42 | resolve | | flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:34:91:40 | resolve | | flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:41:100:47 | resolve | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:15:103:21 | resolve | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:15:105:21 | resolve | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | @@ -126,6 +138,7 @@ test_PromiseDefinition_getResolveParameter test_PromiseDefinition_getACatchHandler | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | | flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:56:103:75 | x => {return source} | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | flow | flow.js:2:15:2:22 | "source" | flow.js:5:7:5:14 | await p1 | @@ -150,4 +163,6 @@ flow | flow.js:2:15:2:22 | "source" | flow.js:84:21:84:21 | e | | flow.js:2:15:2:22 | "source" | flow.js:89:45:89:45 | e | | flow.js:2:15:2:22 | "source" | flow.js:101:7:101:9 | foo | +| flow.js:2:15:2:22 | "source" | flow.js:103:93:103:93 | x | +| flow.js:2:15:2:22 | "source" | flow.js:105:95:105:95 | x | | interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | From fe0b6a86d7d549092eb60f1878ef68e1ba5506d1 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 21 Jan 2020 16:15:18 +0100 Subject: [PATCH 026/148] add data-flow steps for when Promise handlers return other promises --- .../ql/src/semmle/javascript/Promises.qll | 16 ++++++- .../ql/test/library-tests/Promises/flow.js | 22 +++++++++ .../library-tests/Promises/tests.expected | 45 +++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index f5404b21678..03d3ed66f50 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -227,6 +227,11 @@ private module PromiseFlow { prop = rejectField() and pred = getReceiver() and succ = this + or + // read the value of a resolved/rejected promise that is returned + (prop = rejectField() or prop = resolveField()) and + pred = getCallback(0).getAReturn() and + succ = this } override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { @@ -258,6 +263,11 @@ private module PromiseFlow { prop = resolveField() and pred = getReceiver().getALocalSource() and succ = this + or + // read the value of a resolved/rejected promise that is returned + (prop = rejectField() or prop = resolveField()) and + pred = getCallback(0).getAReturn() and + succ = this } override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { @@ -283,9 +293,13 @@ private module PromiseFlow { (prop = resolveField() or prop = rejectField()) and pred = getReceiver() and succ = this + or + // read the value of a rejected promise that is returned + prop = rejectField() and + pred = getCallback(0).getAReturn() and + succ = this } - // a similar thing can also happen if a rejected promise is returned. override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and pred = getCallback(0).getExceptionalReturn() and diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js index 51e29398ed9..62fec0438e1 100644 --- a/javascript/ql/test/library-tests/Promises/flow.js +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -103,4 +103,26 @@ new Promise((resolve, reject) => reject("BLA")).catch(x => {return source}).then(x => sink(x)); // NOT OK new Promise((resolve, reject) => reject("BLA")).finally(x => {throw source}).catch(x => sink(x)); // NOT OK + + var rejected = new Promise((resolve, reject) => reject(source)); + + new Promise((resolve, reject) => reject("BLA")).finally(x => rejected).catch(x => sink(x)); // NOT OK + + new Promise((resolve, reject) => reject("BLA")).catch(x => rejected).then(x => sink(x)) // OK + + new Promise((resolve, reject) => reject("BLA")).catch(x => rejected).catch(x => sink(x)) // NOT OK + + var resolved = Promise.resolve(source); + + new Promise((resolve, reject) => reject("BLA")).catch(x => resolved).catch(x => sink(x)) // OK + + new Promise((resolve, reject) => reject("BLA")).catch(x => resolved).then(x => sink(x)) // NOT OK + + Promise.resolve(123).then(x => resolved).catch(x => sink(x)) // OK + + Promise.resolve(123).then(x => resolved).then(x => sink(x)) // NOT OK + + Promise.resolve(123).then(x => rejected).catch(x => sink(x)) // NOT OK + + Promise.resolve(123).then(x => rejected).then(x => sink(x)) // OK })(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 211d3ea3091..6910ba74e05 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -10,6 +10,11 @@ test_ResolvedPromiseDefinition | flow.js:46:2:46:24 | Promise ... source) | flow.js:46:18:46:23 | source | | flow.js:51:10:51:29 | Promise.resolve(src) | flow.js:51:26:51:28 | src | | flow.js:81:23:81:45 | Promise ... source) | flow.js:81:39:81:44 | source | +| flow.js:115:17:115:39 | Promise ... source) | flow.js:115:33:115:38 | source | +| flow.js:121:2:121:21 | Promise.resolve(123) | flow.js:121:18:121:20 | 123 | +| flow.js:123:2:123:21 | Promise.resolve(123) | flow.js:123:18:123:20 | 123 | +| flow.js:125:2:125:21 | Promise.resolve(123) | flow.js:125:18:125:20 | 123 | +| flow.js:127:2:127:21 | Promise.resolve(123) | flow.js:127:18:127:20 | 123 | | promises.js:53:19:53:41 | Promise ... source) | promises.js:53:35:53:40 | source | | promises.js:62:19:62:41 | Promise ... source) | promises.js:62:35:62:40 | source | | promises.js:71:5:71:27 | Promise ... source) | promises.js:71:21:71:26 | source | @@ -20,6 +25,11 @@ test_PromiseDefinition_getARejectHandler | flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | | flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:56:103:75 | x => {return source} | | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:56:111:68 | x => rejected | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:56:113:68 | x => rejected | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:56:117:68 | x => resolved | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:56:119:68 | x => resolved | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:20:6:22:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | @@ -42,6 +52,12 @@ test_PromiseDefinition_getExecutor | flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:40:100:74 | (resolv ... source) | | flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:14:103:47 | (resolv ... ("BLA") | | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:14:105:47 | (resolv ... ("BLA") | +| flow.js:107:17:107:64 | new Pro ... ource)) | flow.js:107:29:107:63 | (resolv ... source) | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:14:109:47 | (resolv ... ("BLA") | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:14:111:47 | (resolv ... ("BLA") | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:14:113:47 | (resolv ... ("BLA") | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:14:117:47 | (resolv ... ("BLA") | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:14:119:47 | (resolv ... ("BLA") | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | @@ -49,6 +65,7 @@ test_PromiseDefinition_getExecutor | promises.js:43:19:45:6 | Q.Promi ... \\n }) | promises.js:43:29:45:5 | functio ... ;\\n } | test_PromiseDefinition_getAFinallyHandler | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | test_PromiseDefinition | flow.js:7:11:7:59 | new Pro ... ource)) | @@ -69,6 +86,12 @@ test_PromiseDefinition | flow.js:100:28:100:75 | new Pro ... ource)) | | flow.js:103:2:103:48 | new Pro ... "BLA")) | | flow.js:105:2:105:48 | new Pro ... "BLA")) | +| flow.js:107:17:107:64 | new Pro ... ource)) | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | @@ -84,6 +107,7 @@ test_PromiseDefinition_getAResolveHandler | flow.js:74:10:74:57 | new Pro ... ource)) | flow.js:74:64:74:71 | () => {} | | flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:75:91:82 | () => {} | | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | @@ -107,6 +131,12 @@ test_PromiseDefinition_getRejectParameter | flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:50:100:55 | reject | | flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:24:103:29 | reject | | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:24:105:29 | reject | +| flow.js:107:17:107:64 | new Pro ... ource)) | flow.js:107:39:107:44 | reject | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:24:109:29 | reject | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:24:111:29 | reject | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:24:113:29 | reject | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:24:117:29 | reject | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:24:119:29 | reject | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | @@ -130,6 +160,12 @@ test_PromiseDefinition_getResolveParameter | flow.js:100:28:100:75 | new Pro ... ource)) | flow.js:100:41:100:47 | resolve | | flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:15:103:21 | resolve | | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:15:105:21 | resolve | +| flow.js:107:17:107:64 | new Pro ... ource)) | flow.js:107:30:107:36 | resolve | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:15:109:21 | resolve | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:15:111:21 | resolve | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:15:113:21 | resolve | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:15:117:21 | resolve | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:15:119:21 | resolve | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | @@ -139,6 +175,10 @@ test_PromiseDefinition_getACatchHandler | flow.js:32:2:32:49 | new Pro ... ource)) | flow.js:32:57:32:68 | x => sink(x) | | flow.js:48:2:48:36 | new Pro ... urce }) | flow.js:48:44:48:55 | x => sink(x) | | flow.js:103:2:103:48 | new Pro ... "BLA")) | flow.js:103:56:103:75 | x => {return source} | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | flow.js:111:56:111:68 | x => rejected | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:56:113:68 | x => rejected | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:56:117:68 | x => resolved | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:56:119:68 | x => resolved | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:23:18:25:3 | (v) => ... v;\\n } | flow | flow.js:2:15:2:22 | "source" | flow.js:5:7:5:14 | await p1 | @@ -165,4 +205,9 @@ flow | flow.js:2:15:2:22 | "source" | flow.js:101:7:101:9 | foo | | flow.js:2:15:2:22 | "source" | flow.js:103:93:103:93 | x | | flow.js:2:15:2:22 | "source" | flow.js:105:95:105:95 | x | +| flow.js:2:15:2:22 | "source" | flow.js:109:89:109:89 | x | +| flow.js:2:15:2:22 | "source" | flow.js:113:87:113:87 | x | +| flow.js:2:15:2:22 | "source" | flow.js:119:86:119:86 | x | +| flow.js:2:15:2:22 | "source" | flow.js:123:58:123:58 | x | +| flow.js:2:15:2:22 | "source" | flow.js:125:59:125:59 | x | | interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | From 86791326245ba8be4260a928784188667886cc8b Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 21 Jan 2020 18:00:06 +0100 Subject: [PATCH 027/148] copy data from both callbacks in Promise data-flow --- javascript/ql/src/semmle/javascript/Promises.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 03d3ed66f50..480dce298c2 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -230,7 +230,7 @@ private module PromiseFlow { or // read the value of a resolved/rejected promise that is returned (prop = rejectField() or prop = resolveField()) and - pred = getCallback(0).getAReturn() and + pred = getCallback([0..1]).getAReturn() and succ = this } From 8370699344a04608d30357d5208a9bf48b244b55 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 21 Jan 2020 20:11:27 +0100 Subject: [PATCH 028/148] add support for creating a promise with another resolved promise, e.g: Promise.resolve(otherPromise) --- .../ql/src/semmle/javascript/Promises.qll | 14 +++++++++++ .../ql/test/library-tests/Promises/flow.js | 4 ++++ .../ql/test/library-tests/Promises/flow.qll | 23 ++++++++++++++++--- .../library-tests/Promises/tests.expected | 9 ++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index 480dce298c2..e59f77924cb 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -162,6 +162,13 @@ private module PromiseFlow { ) and succ = this } + + override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + // Copy the value of a resolved promise to the value of this promise. + prop = resolveField() and + pred = promise.getResolveParameter().getACall().getArgument(0) and + succ = this + } } /** @@ -178,6 +185,13 @@ private module PromiseFlow { pred = promise.getValue() and succ = this } + + override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + // Copy the value of a resolved promise to the value of this promise. + prop = resolveField() and + pred = promise.getValue() and + succ = this + } } diff --git a/javascript/ql/test/library-tests/Promises/flow.js b/javascript/ql/test/library-tests/Promises/flow.js index 62fec0438e1..d92ef541b85 100644 --- a/javascript/ql/test/library-tests/Promises/flow.js +++ b/javascript/ql/test/library-tests/Promises/flow.js @@ -125,4 +125,8 @@ Promise.resolve(123).then(x => rejected).catch(x => sink(x)) // NOT OK Promise.resolve(123).then(x => rejected).then(x => sink(x)) // OK + + new Promise((resolve, reject) => resolve(resolved)).then(x => sink(x)); // NOT OK + + Promise.resolve(resolved).then(x => sink(x)); // NOT OK })(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/flow.qll b/javascript/ql/test/library-tests/Promises/flow.qll index 60118aed6b5..395d5cb88a8 100644 --- a/javascript/ql/test/library-tests/Promises/flow.qll +++ b/javascript/ql/test/library-tests/Promises/flow.qll @@ -1,7 +1,19 @@ import javascript -class Configuration extends TaintTracking::Configuration { - Configuration() { this = "PromiseFlowTestingConfig" } +class Configuration extends DataFlow::Configuration { + Configuration() { this = "PromiseDataFlowFlowTestingConfig" } + + override predicate isSource(DataFlow::Node source) { + source.getEnclosingExpr().getStringValue() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + any(DataFlow::InvokeNode call | call.getCalleeName() = "sink").getAnArgument() = sink + } +} + +class TaintConfig extends TaintTracking::Configuration { + TaintConfig() { this = "PromiseTaintFlowTestingConfig" } override predicate isSource(DataFlow::Node source) { source.getEnclosingExpr().getStringValue() = "source" @@ -13,5 +25,10 @@ class Configuration extends TaintTracking::Configuration { } query predicate flow(DataFlow::Node source, DataFlow::Node sink) { - any(Configuration a).hasFlow(source, sink) + any(Configuration c).hasFlow(source, sink) } + +query predicate exclusiveTaintFlow(DataFlow::Node source, DataFlow::Node sink) { + not any(Configuration c).hasFlow(source, sink) and + any(TaintConfig c).hasFlow(source, sink) +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Promises/tests.expected b/javascript/ql/test/library-tests/Promises/tests.expected index 6910ba74e05..f4ee1263be5 100644 --- a/javascript/ql/test/library-tests/Promises/tests.expected +++ b/javascript/ql/test/library-tests/Promises/tests.expected @@ -15,6 +15,7 @@ test_ResolvedPromiseDefinition | flow.js:123:2:123:21 | Promise.resolve(123) | flow.js:123:18:123:20 | 123 | | flow.js:125:2:125:21 | Promise.resolve(123) | flow.js:125:18:125:20 | 123 | | flow.js:127:2:127:21 | Promise.resolve(123) | flow.js:127:18:127:20 | 123 | +| flow.js:131:2:131:26 | Promise ... solved) | flow.js:131:18:131:25 | resolved | | promises.js:53:19:53:41 | Promise ... source) | promises.js:53:35:53:40 | source | | promises.js:62:19:62:41 | Promise ... source) | promises.js:62:35:62:40 | source | | promises.js:71:5:71:27 | Promise ... source) | promises.js:71:21:71:26 | source | @@ -58,6 +59,7 @@ test_PromiseDefinition_getExecutor | flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:14:113:47 | (resolv ... ("BLA") | | flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:14:117:47 | (resolv ... ("BLA") | | flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:14:119:47 | (resolv ... ("BLA") | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:14:129:51 | (resolv ... solved) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:24:15:5 | functio ... ;\\n } | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:29:5:3 | functio ... e);\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:30:17:3 | (res, r ... e);\\n } | @@ -92,6 +94,7 @@ test_PromiseDefinition | flow.js:113:2:113:48 | new Pro ... "BLA")) | | flow.js:117:2:117:48 | new Pro ... "BLA")) | | flow.js:119:2:119:48 | new Pro ... "BLA")) | +| flow.js:129:2:129:52 | new Pro ... olved)) | | interflow.js:11:12:15:6 | new Pro ... \\n }) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | | promises.js:10:18:17:4 | new Pro ... );\\n }) | @@ -108,6 +111,7 @@ test_PromiseDefinition_getAResolveHandler | flow.js:91:21:91:68 | new Pro ... ource)) | flow.js:91:75:91:82 | () => {} | | flow.js:105:2:105:48 | new Pro ... "BLA")) | flow.js:105:58:105:76 | x => {throw source} | | flow.js:109:2:109:48 | new Pro ... "BLA")) | flow.js:109:58:109:70 | x => rejected | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:59:129:70 | x => sink(x) | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:6:16:8:3 | functio ... al;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:18:17:20:3 | (v) => ... v;\\n } | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:26:20:28:3 | (v) => ... v;\\n } | @@ -137,6 +141,7 @@ test_PromiseDefinition_getRejectParameter | flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:24:113:29 | reject | | flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:24:117:29 | reject | | flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:24:119:29 | reject | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:24:129:29 | reject | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:43:11:48 | reject | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:48:3:53 | reject | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:36:10:38 | rej | @@ -166,6 +171,7 @@ test_PromiseDefinition_getResolveParameter | flow.js:113:2:113:48 | new Pro ... "BLA")) | flow.js:113:15:113:21 | resolve | | flow.js:117:2:117:48 | new Pro ... "BLA")) | flow.js:117:15:117:21 | resolve | | flow.js:119:2:119:48 | new Pro ... "BLA")) | flow.js:119:15:119:21 | resolve | +| flow.js:129:2:129:52 | new Pro ... olved)) | flow.js:129:15:129:21 | resolve | | interflow.js:11:12:15:6 | new Pro ... \\n }) | interflow.js:11:34:11:40 | resolve | | promises.js:3:17:5:4 | new Pro ... );\\n }) | promises.js:3:39:3:45 | resolve | | promises.js:10:18:17:4 | new Pro ... );\\n }) | promises.js:10:31:10:33 | res | @@ -210,4 +216,7 @@ flow | flow.js:2:15:2:22 | "source" | flow.js:119:86:119:86 | x | | flow.js:2:15:2:22 | "source" | flow.js:123:58:123:58 | x | | flow.js:2:15:2:22 | "source" | flow.js:125:59:125:59 | x | +| flow.js:2:15:2:22 | "source" | flow.js:129:69:129:69 | x | +| flow.js:2:15:2:22 | "source" | flow.js:131:43:131:43 | x | +exclusiveTaintFlow | interflow.js:3:18:3:25 | "source" | interflow.js:18:10:18:14 | error | From 5063e3820d3c32119978f5e0518a9c82f302df1f Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 22 Jan 2020 11:18:47 +0100 Subject: [PATCH 029/148] update expected output --- .../InterProceduralFlow/DataFlow.expected | 4 +++ .../InterProceduralFlow/GermanFlow.expected | 4 +++ .../InterProceduralFlow/TrackedNodes.expected | 12 ++++++++ .../Promises/AdditionalPromises.expected | 28 +++++++++++++++++++ .../Security/CWE-079/ReflectedXss.expected | 6 ++++ 5 files changed, 54 insertions(+) diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected index 5b5ac277940..24e5f967f9f 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/DataFlow.expected @@ -22,8 +22,12 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | +| promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | +| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | +| promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | +| promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | | properties.js:2:16:2:24 | "tainted" | properties.js:5:14:5:23 | a.someProp | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected index c10ab912bf2..9c3980797ea 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/GermanFlow.expected @@ -23,8 +23,12 @@ | partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x | | partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x | +| promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | +| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | +| promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | promises.js:12:22:12:31 | "rejected" | promises.js:24:20:24:20 | v | +| promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | | properties2.js:7:14:7:21 | "source" | properties2.js:8:12:8:24 | foo(source).p | | properties2.js:7:14:7:21 | "source" | properties2.js:33:13:33:20 | getP(o3) | | properties.js:2:16:2:24 | "tainted" | properties.js:5:14:5:23 | a.someProp | diff --git a/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected b/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected index 9aa7cc76049..7c8c2ca4b45 100644 --- a/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected +++ b/javascript/ql/test/library-tests/InterProceduralFlow/TrackedNodes.expected @@ -1,7 +1,17 @@ +| missing | promises.js:1:2:1:2 | source | promises.js:6:26:6:28 | val | +| missing | promises.js:1:2:1:2 | source | promises.js:7:16:7:18 | val | +| missing | promises.js:1:2:1:2 | source | promises.js:37:11:37:11 | v | +| missing | promises.js:1:2:1:2 | source | promises.js:38:32:38:32 | v | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:6:26:6:28 | val | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:37:11:37:11 | v | +| missing | promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v | | missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:20:7:20:7 | v | | missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:21:20:21:20 | v | | missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:23:19:23:19 | v | | missing | promises.js:10:30:17:3 | exceptional return of anonymous function | promises.js:24:20:24:20 | v | +| missing | promises.js:11:22:11:31 | "resolved" | promises.js:18:18:18:18 | v | +| missing | promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v | | missing | promises.js:12:22:12:31 | "rejected" | promises.js:20:7:20:7 | v | | missing | promises.js:12:22:12:31 | "rejected" | promises.js:21:20:21:20 | v | | missing | promises.js:12:22:12:31 | "rejected" | promises.js:23:19:23:19 | v | @@ -18,3 +28,5 @@ | missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:21:20:21:20 | v | | missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:23:19:23:19 | v | | missing | promises.js:16:7:16:21 | exceptional return of rej(rej_source) | promises.js:24:20:24:20 | v | +| missing | promises.js:32:24:32:37 | "also tainted" | promises.js:37:11:37:11 | v | +| missing | promises.js:32:24:32:37 | "also tainted" | promises.js:38:32:38:32 | v | diff --git a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected index 0159e921700..1c1f14deabe 100644 --- a/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected +++ b/javascript/ql/test/library-tests/Promises/AdditionalPromises.expected @@ -37,6 +37,34 @@ | flow.js:76:2:76:17 | chainedPromise() | | flow.js:76:2:76:32 | chained ... => {}) | | flow.js:86:23:86:70 | new Pro ... ource)) | +| flow.js:89:3:89:27 | ("foo", ... => {}) | +| flow.js:91:21:91:68 | new Pro ... ource)) | +| flow.js:100:28:100:75 | new Pro ... ource)) | +| flow.js:103:2:103:48 | new Pro ... "BLA")) | +| flow.js:103:2:103:76 | new Pro ... ource}) | +| flow.js:105:2:105:48 | new Pro ... "BLA")) | +| flow.js:105:2:105:77 | new Pro ... ource}) | +| flow.js:107:17:107:64 | new Pro ... ource)) | +| flow.js:109:2:109:48 | new Pro ... "BLA")) | +| flow.js:109:2:109:71 | new Pro ... jected) | +| flow.js:111:2:111:48 | new Pro ... "BLA")) | +| flow.js:111:2:111:69 | new Pro ... jected) | +| flow.js:113:2:113:48 | new Pro ... "BLA")) | +| flow.js:113:2:113:69 | new Pro ... jected) | +| flow.js:117:2:117:48 | new Pro ... "BLA")) | +| flow.js:117:2:117:69 | new Pro ... solved) | +| flow.js:119:2:119:48 | new Pro ... "BLA")) | +| flow.js:119:2:119:69 | new Pro ... solved) | +| flow.js:121:2:121:21 | Promise.resolve(123) | +| flow.js:121:2:121:41 | Promise ... solved) | +| flow.js:123:2:123:21 | Promise.resolve(123) | +| flow.js:123:2:123:41 | Promise ... solved) | +| flow.js:125:2:125:21 | Promise.resolve(123) | +| flow.js:125:2:125:41 | Promise ... jected) | +| flow.js:127:2:127:21 | Promise.resolve(123) | +| flow.js:127:2:127:41 | Promise ... jected) | +| flow.js:129:2:129:52 | new Pro ... olved)) | +| flow.js:131:2:131:26 | Promise ... solved) | | interflow.js:6:3:6:25 | loadScr ... urce()) | | interflow.js:6:3:7:26 | loadScr ... () { }) | | interflow.js:6:3:8:26 | loadScr ... () { }) | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected index b5b4b7aec1e..bcf54b75580 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected @@ -46,6 +46,8 @@ nodes | promises.js:5:44:5:57 | req.query.data | | promises.js:5:44:5:57 | req.query.data | | promises.js:6:11:6:11 | x | +| promises.js:6:11:6:11 | x | +| promises.js:6:25:6:25 | x | | promises.js:6:25:6:25 | x | | promises.js:6:25:6:25 | x | | tst2.js:6:7:6:30 | p | @@ -106,6 +108,10 @@ edges | promises.js:5:3:5:59 | new Pro ... .data)) | promises.js:6:11:6:11 | x | | promises.js:5:44:5:57 | req.query.data | promises.js:5:3:5:59 | new Pro ... .data)) | | promises.js:5:44:5:57 | req.query.data | promises.js:5:3:5:59 | new Pro ... .data)) | +| promises.js:5:44:5:57 | req.query.data | promises.js:6:11:6:11 | x | +| promises.js:5:44:5:57 | req.query.data | promises.js:6:11:6:11 | x | +| promises.js:6:11:6:11 | x | promises.js:6:25:6:25 | x | +| promises.js:6:11:6:11 | x | promises.js:6:25:6:25 | x | | promises.js:6:11:6:11 | x | promises.js:6:25:6:25 | x | | promises.js:6:11:6:11 | x | promises.js:6:25:6:25 | x | | tst2.js:6:7:6:30 | p | tst2.js:7:12:7:12 | p | From dde0f868b3fb2785e7983fa9243abe60e94dc86f Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 7 Jan 2020 13:15:47 +0000 Subject: [PATCH 030/148] TS: Handle monorepos by rewriting package.json --- .../com/semmle/js/extractor/AutoBuild.java | 176 +++++++++++++++--- .../js/extractor/test/AutoBuildTests.java | 9 +- 2 files changed, 159 insertions(+), 26 deletions(-) diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index 9f52be05cb6..a93e776a008 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -1,5 +1,11 @@ package com.semmle.js.extractor; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; import com.semmle.js.extractor.ExtractorConfig.SourceType; import com.semmle.js.extractor.FileExtractor.FileType; import com.semmle.js.extractor.trapcache.DefaultTrapCache; @@ -16,6 +22,7 @@ import com.semmle.util.exception.ResourceError; import com.semmle.util.exception.UserError; import com.semmle.util.extraction.ExtractorOutputConfig; import com.semmle.util.files.FileUtil; +import com.semmle.util.io.WholeIO; import com.semmle.util.io.csv.CSVReader; import com.semmle.util.language.LegacyLanguage; import com.semmle.util.process.Env; @@ -26,6 +33,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; +import java.io.Writer; import java.lang.ProcessBuilder.Redirect; import java.net.URI; import java.net.URISyntaxException; @@ -37,9 +45,11 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -48,6 +58,7 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; import java.util.stream.Stream; /** @@ -391,6 +402,7 @@ public class AutoBuild { // include .eslintrc files and package.json files patterns.add("**/.eslintrc*"); patterns.add("**/package.json"); + patterns.add("**/tsconfig.json"); // include any explicitly specified extensions for (String extension : fileTypes.keySet()) patterns.add("**/*" + extension); @@ -545,12 +557,18 @@ public class AutoBuild { List tsconfigFiles = new ArrayList<>(); findFilesToExtract(defaultExtractor, filesToExtract, tsconfigFiles); + Map originalFiles = Collections.emptyMap(); if (!tsconfigFiles.isEmpty() && this.installDependencies) { - this.installDependencies(filesToExtract); + originalFiles = this.installDependencies(filesToExtract); } // extract TypeScript projects and files - Set extractedFiles = extractTypeScript(defaultExtractor, filesToExtract, tsconfigFiles); + Set extractedFiles; + try { + extractedFiles = extractTypeScript(defaultExtractor, filesToExtract, tsconfigFiles); + } finally { + restoreOriginalFiles(originalFiles); + } // extract remaining files for (Path f : filesToExtract) { @@ -587,36 +605,143 @@ public class AutoBuild { } } - protected void installDependencies(Set filesToExtract) { + protected void restoreOriginalFiles(Map originalFiles) { + originalFiles.forEach( + (file, original) -> { + try { + Files.move(original, file, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new ResourceError("Could not restore original file: " + file, e); + } + }); + } + + private static final Pattern validPackageName = Pattern.compile("(@[\\w.-]+/)?\\w[\\w.-]*"); + + protected Map installDependencies(Set filesToExtract) { if (!verifyYarnInstallation()) { - return; + return Collections.emptyMap(); } + + Path rootNodeModules = Paths.get("node_modules"); + + // Read all package.json files, and install symlinks to them. + Map packageJsonFiles = new LinkedHashMap<>(); + Set packagesInRepo = new LinkedHashSet<>(); for (Path file : filesToExtract) { if (file.getFileName().toString().equals("package.json")) { - System.out.println("Installing dependencies from " + file); - ProcessBuilder pb = - new ProcessBuilder( - Arrays.asList( - "yarn", - "install", - "--non-interactive", - "--ignore-scripts", - "--ignore-platform", - "--ignore-engines", - "--ignore-optional", - "--no-default-rc", - "--no-bin-links", - "--pure-lockfile")); - pb.directory(file.getParent().toFile()); - pb.redirectOutput(Redirect.INHERIT); - pb.redirectError(Redirect.INHERIT); try { - pb.start().waitFor(this.installDependenciesTimeout, TimeUnit.MILLISECONDS); - } catch (IOException | InterruptedException ex) { - throw new ResourceError("Could not install dependencies from " + file, ex); + String text = new WholeIO().read(file); + JsonElement json = new JsonParser().parse(text); + if (!(json instanceof JsonObject)) continue; + JsonObject jsonObject = (JsonObject) json; + file = file.toAbsolutePath(); + packageJsonFiles.put(file, jsonObject); + + JsonElement nameElm = jsonObject.get("name"); + if (nameElm instanceof JsonPrimitive && ((JsonPrimitive) nameElm).isString()) { + String name = nameElm.getAsString(); + packagesInRepo.add(name); + + if (validPackageName.matcher(name).matches()) { + // Create a symlink to the package: /node_modules/foo -> /path/to/foo + try { + Path symlinkPath = rootNodeModules.resolve(name); + if (!Files.exists(symlinkPath)) { + Files.createDirectories(symlinkPath.getParent()); + Files.createSymbolicLink(symlinkPath, file.getParent()); + } else { + // If node_modules/foo already exists, presumably it contains the right thing, + // so just continue extraction with that in place. + } + } catch (IOException e) { + throw new ResourceError("Could not install symlink to package " + file, e); + } + } + } + } catch (JsonParseException e) { + System.err.println("Could not parse JSON file: " + file); + System.err.println(e); + // Continue without the malformed package.json file } } } + + // Remove all dependencies on local packages from package.json files so yarn doesn't + // try to download them. Yarn would fail otherwise if these packages were never published. + // These packages will instead be found through the symlink we installed in node_modules above. + // Note that we ignore optional dependencies during installation, so "optionalDependencies" + // is ignored here as well. + final List dependencyFields = + Arrays.asList("dependencies", "devDependencies", "peerDependencies"); + final Set filesToChange = new LinkedHashSet<>(); + packageJsonFiles.forEach( + (path, packageJson) -> { + for (String dependencyField : dependencyFields) { + JsonElement dependencyElm = packageJson.get(dependencyField); + if (!(dependencyElm instanceof JsonObject)) continue; + JsonObject dependencyObj = (JsonObject) dependencyElm; + for (String packageName : packagesInRepo) { + if (!dependencyObj.has(packageName)) continue; + dependencyObj.remove(packageName); + filesToChange.add(path); + } + } + // Override "main" to point at the source folder instead of the output directory. + // TypeScript uses this field to locate the contents of a package. + // We simply guess that "./src" contains the source code, if it exists. + if (Files.exists(path.getParent().resolve("src"))) { + packageJson.addProperty("main", "./src"); + filesToChange.add(path); + } + }); + + // Write the new package.json files to disk + Map originalFiles = new LinkedHashMap<>(); + final String backupFilename = "package.json.lgtm.backup"; + for (Path file : filesToChange) { + Path backup = file.resolveSibling(backupFilename); + try { + originalFiles.put(file, backup); + Files.move(file, backup, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + throw new ResourceError("Could not backup package.json file: " + file, e); + } + try (Writer writer = Files.newBufferedWriter(file)) { + new Gson().toJson(packageJsonFiles.get(file), writer); + } catch (IOException e) { + throw new ResourceError("Could not rewrite package.json file: " + file, e); + } + } + + // Install dependencies + for (Path file : packageJsonFiles.keySet()) { + System.out.println("Installing dependencies from " + file); + ProcessBuilder pb = + new ProcessBuilder( + Arrays.asList( + "yarn", + "install", + "--non-interactive", + "--ignore-scripts", + "--ignore-platform", + "--ignore-engines", + "--ignore-optional", + "--no-default-rc", + "--no-bin-links", + "--pure-lockfile")); + pb.directory(file.getParent().toFile()); + pb.redirectOutput(Redirect.INHERIT); + pb.redirectError(Redirect.INHERIT); + try { + pb.start().waitFor(this.installDependenciesTimeout, TimeUnit.MILLISECONDS); + } catch (IOException | InterruptedException ex) { + restoreOriginalFiles(originalFiles); // Try to clean up before giving up. + throw new ResourceError("Could not install dependencies from " + file, ex); + } + } + + return originalFiles; } private ExtractorConfig mkExtractorConfig() { @@ -729,7 +854,8 @@ public class AutoBuild { // extract TypeScript projects from 'tsconfig.json' if (typeScriptMode == TypeScriptMode.FULL && file.getFileName().endsWith("tsconfig.json") - && !excludes.contains(file)) { + && !excludes.contains(file) + && isFileIncluded(file)) { tsconfigFiles.add(file); } diff --git a/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java b/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java index 3e39cfc6706..f78bec0c32d 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java +++ b/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java @@ -20,6 +20,7 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.DosFileAttributeView; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -129,8 +130,14 @@ public class AutoBuildTests { } @Override - protected void installDependencies(Set filesToExtract) { + protected void restoreOriginalFiles(java.util.Map originalFiles) { + // Do nothing + } + + @Override + protected Map installDependencies(Set filesToExtract) { // never install dependencies during testing + return Collections.emptyMap(); } @Override From 71b540755d1be28a3d16243e06c47f0dcf81d622 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Fri, 17 Jan 2020 10:51:39 +0000 Subject: [PATCH 031/148] TS: Print TypeScript semantic errors in log --- javascript/extractor/lib/typescript/src/main.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index c260bb321d9..0a2da694869 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -262,6 +262,23 @@ function handleOpenProjectCommand(command: OpenProjectCommand) { let program = project.program; let typeChecker = program.getTypeChecker(); + let diagnostics = program.getSemanticDiagnostics() + .filter(d => d.category === ts.DiagnosticCategory.Error); + console.warn('TypeScript: reported ' + diagnostics.length + ' semantic errors.'); + for (let diagnostic of diagnostics) { + let text = diagnostic.messageText; + if (typeof text !== 'string') { + text = text.messageText; + } + let locationStr = ''; + let { file } = diagnostic; + if (file != null) { + let { line, character } = file.getLineAndCharacterOfPosition(diagnostic.start); + locationStr = `${file.fileName}:${line}:${character}`; + } + console.warn(`TypeScript: ${locationStr} ${text}`); + } + // Associate external module names with the corresponding file symbols. // We need these mappings to identify which module a given external type comes from. // The TypeScript API lets us resolve a module name to a source file, but there is no From 21eecc4c9c0d69319583b81e33ec3a5199ec1d01 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 16 Jan 2020 15:04:57 +0000 Subject: [PATCH 032/148] JS: Make return type class for installDependencies() --- .../com/semmle/js/extractor/AutoBuild.java | 76 +++++++++---------- .../DependencyInstallationResult.java | 26 +++++++ .../js/extractor/test/AutoBuildTests.java | 26 ++++--- 3 files changed, 78 insertions(+), 50 deletions(-) create mode 100644 javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index a93e776a008..d357c1fdc61 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -1,5 +1,37 @@ package com.semmle.js.extractor; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.Writer; +import java.lang.ProcessBuilder.Redirect; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; +import java.util.stream.Stream; + import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -28,38 +60,6 @@ import com.semmle.util.language.LegacyLanguage; import com.semmle.util.process.Env; import com.semmle.util.projectstructure.ProjectLayout; import com.semmle.util.trap.TrapWriter; -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.Writer; -import java.lang.ProcessBuilder.Redirect; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileVisitResult; -import java.nio.file.FileVisitor; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; -import java.util.stream.Stream; /** * An alternative entry point to the JavaScript extractor. @@ -557,9 +557,9 @@ public class AutoBuild { List tsconfigFiles = new ArrayList<>(); findFilesToExtract(defaultExtractor, filesToExtract, tsconfigFiles); - Map originalFiles = Collections.emptyMap(); + DependencyInstallationResult dependencyInstallationResult = DependencyInstallationResult.empty; if (!tsconfigFiles.isEmpty() && this.installDependencies) { - originalFiles = this.installDependencies(filesToExtract); + dependencyInstallationResult = this.installDependencies(filesToExtract); } // extract TypeScript projects and files @@ -567,7 +567,7 @@ public class AutoBuild { try { extractedFiles = extractTypeScript(defaultExtractor, filesToExtract, tsconfigFiles); } finally { - restoreOriginalFiles(originalFiles); + restoreOriginalFiles(dependencyInstallationResult.getOriginalFiles()); } // extract remaining files @@ -618,9 +618,9 @@ public class AutoBuild { private static final Pattern validPackageName = Pattern.compile("(@[\\w.-]+/)?\\w[\\w.-]*"); - protected Map installDependencies(Set filesToExtract) { + protected DependencyInstallationResult installDependencies(Set filesToExtract) { if (!verifyYarnInstallation()) { - return Collections.emptyMap(); + return DependencyInstallationResult.empty; } Path rootNodeModules = Paths.get("node_modules"); @@ -741,7 +741,7 @@ public class AutoBuild { } } - return originalFiles; + return new DependencyInstallationResult(originalFiles); } private ExtractorConfig mkExtractorConfig() { diff --git a/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java b/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java new file mode 100644 index 00000000000..74b3d4e459d --- /dev/null +++ b/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java @@ -0,0 +1,26 @@ +package com.semmle.js.extractor; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.Map; + +/** + * Contains the results of installing dependencies. + */ +public class DependencyInstallationResult { + private Map originalFiles; + + public static final DependencyInstallationResult empty = new DependencyInstallationResult(Collections.emptyMap()); + + public DependencyInstallationResult(Map originalFiles) { + this.originalFiles = originalFiles; + } + + /** + * Returns the mapping from files left behind by dependency installation to + * the backups of those files, to be restored after extraction. + */ + public Map getOriginalFiles() { + return originalFiles; + } +} diff --git a/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java b/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java index f78bec0c32d..dfe9c789197 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java +++ b/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java @@ -1,14 +1,5 @@ package com.semmle.js.extractor.test; -import com.semmle.js.extractor.AutoBuild; -import com.semmle.js.extractor.ExtractorState; -import com.semmle.js.extractor.FileExtractor; -import com.semmle.js.extractor.FileExtractor.FileType; -import com.semmle.util.data.StringUtil; -import com.semmle.util.exception.UserError; -import com.semmle.util.files.FileUtil; -import com.semmle.util.files.FileUtil8; -import com.semmle.util.process.Env; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -20,18 +11,29 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.DosFileAttributeView; import java.util.ArrayList; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; + import org.junit.After; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Test; +import com.semmle.js.extractor.AutoBuild; +import com.semmle.js.extractor.DependencyInstallationResult; +import com.semmle.js.extractor.ExtractorState; +import com.semmle.js.extractor.FileExtractor; +import com.semmle.js.extractor.FileExtractor.FileType; +import com.semmle.util.data.StringUtil; +import com.semmle.util.exception.UserError; +import com.semmle.util.files.FileUtil; +import com.semmle.util.files.FileUtil8; +import com.semmle.util.process.Env; + public class AutoBuildTests { private Path SEMMLE_DIST, LGTM_SRC; private Set expected; @@ -135,9 +137,9 @@ public class AutoBuildTests { } @Override - protected Map installDependencies(Set filesToExtract) { + protected DependencyInstallationResult installDependencies(Set filesToExtract) { // never install dependencies during testing - return Collections.emptyMap(); + return DependencyInstallationResult.empty; } @Override From b92203a87f520ad279c7c226d138aaacaa239b98 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 22 Jan 2020 12:04:42 +0100 Subject: [PATCH 033/148] Java: Allow null literals as sources in data flow. --- .../java/dataflow/internal/DataFlowPrivate.qll | 2 ++ java/ql/test/library-tests/dataflow/null/A.java | 9 +++++++++ .../dataflow/null/testnullflow.expected | 4 ++++ .../library-tests/dataflow/null/testnullflow.ql | 14 ++++++++++++++ 4 files changed, 29 insertions(+) create mode 100644 java/ql/test/library-tests/dataflow/null/A.java create mode 100644 java/ql/test/library-tests/dataflow/null/testnullflow.expected create mode 100644 java/ql/test/library-tests/dataflow/null/testnullflow.ql 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 e41256ac0b0..19515e82804 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowPrivate.qll @@ -235,6 +235,8 @@ DataFlowType getErasedRepr(Type t) { then result.(BoxedType).getPrimitiveType().getName() = "boolean" else result = e ) + or + t instanceof NullType and result instanceof TypeObject } /** Gets a string representation of a type returned by `getErasedRepr`. */ diff --git a/java/ql/test/library-tests/dataflow/null/A.java b/java/ql/test/library-tests/dataflow/null/A.java new file mode 100644 index 00000000000..252358f7996 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/null/A.java @@ -0,0 +1,9 @@ +public class A { + void sink(Object o) { } + + void foo() { + Object src = null; + Object x = src; + sink(x); + } +} diff --git a/java/ql/test/library-tests/dataflow/null/testnullflow.expected b/java/ql/test/library-tests/dataflow/null/testnullflow.expected new file mode 100644 index 00000000000..532d64e81f8 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/null/testnullflow.expected @@ -0,0 +1,4 @@ +| A.java:5:18:5:21 | null | A.java:2:13:2:20 | o | +| A.java:5:18:5:21 | null | A.java:5:18:5:21 | null | +| A.java:5:18:5:21 | null | A.java:6:16:6:18 | src | +| A.java:5:18:5:21 | null | A.java:7:10:7:10 | x | diff --git a/java/ql/test/library-tests/dataflow/null/testnullflow.ql b/java/ql/test/library-tests/dataflow/null/testnullflow.ql new file mode 100644 index 00000000000..d0937e9c0f4 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/null/testnullflow.ql @@ -0,0 +1,14 @@ +import java +import semmle.code.java.dataflow.DataFlow + +class Conf extends DataFlow::Configuration { + Conf() { this = "qqconf" } + + override predicate isSource(DataFlow::Node n) { n.asExpr() instanceof NullLiteral } + + override predicate isSink(DataFlow::Node n) { any() } +} + +from Conf conf, DataFlow::Node src, DataFlow::Node sink +where conf.hasFlow(src, sink) +select src, sink From 303bac971062f65254328725893eada549ca469e Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Fri, 17 Jan 2020 14:34:23 +0000 Subject: [PATCH 034/148] TS: Guess main file location --- .../com/semmle/js/extractor/AutoBuild.java | 93 ++++++++++++++++++- 1 file changed, 89 insertions(+), 4 deletions(-) diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index d357c1fdc61..7d89f8edf88 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -617,6 +617,23 @@ public class AutoBuild { } private static final Pattern validPackageName = Pattern.compile("(@[\\w.-]+/)?\\w[\\w.-]*"); + + private static Path tryResolveWithExtensions(Path dir, String stem, Iterable extensions) { + for (String ext : extensions) { + Path path = dir.resolve(stem + ext); + if (Files.exists(dir.resolve(path))) { + return path; + } + } + return null; + } + + private static Path tryResolveTypeScriptOrJavaScriptFile(Path dir, String stem) { + Path resolved = tryResolveWithExtensions(dir, stem, FileType.TYPESCRIPT.getExtensions()); + if (resolved != null) return resolved; + return tryResolveWithExtensions(dir, stem, FileType.JS.getExtensions()); + } + protected DependencyInstallationResult installDependencies(Set filesToExtract) { if (!verifyYarnInstallation()) { @@ -688,11 +705,14 @@ public class AutoBuild { } } // Override "main" to point at the source folder instead of the output directory. - // TypeScript uses this field to locate the contents of a package. - // We simply guess that "./src" contains the source code, if it exists. - if (Files.exists(path.getParent().resolve("src"))) { - packageJson.addProperty("main", "./src"); + Path entryPoint = guessPackageMainFile(path, packageJson); + if (entryPoint != null) { + System.out.println("Main file for " + path + " set to " + entryPoint); + packageJson.addProperty("main", entryPoint.toString()); + packageJson.remove("typings"); filesToChange.add(path); + } else { + System.out.println("No main file found for " + path); } }); @@ -744,6 +764,71 @@ public class AutoBuild { return new DependencyInstallationResult(originalFiles); } + /** + * Attempts to find a TypeScript file that acts as the main entry point to the + * given package - that is, the file you get when importing the package by name + * without any path suffix. + */ + private Path guessPackageMainFile(Path packageJsonFile, JsonObject packageJson) { + Path packageDir = packageJsonFile.getParent(); + + // Try /index.ts. + // Do not allow JavaScript extensions at this point as it might be compiled output (will be attempted later). + Path resolved = tryResolveWithExtensions(packageDir, "index", FileType.TYPESCRIPT.getExtensions()); + if (resolved != null) { + return resolved; + } + + // Get the "main" property from the package.json + // This usually refers to the compiled output, such as `./out/foo.js` but may hint as to + // the name of main file ("foo" in this case). + String mainStr = null; + JsonElement mainElm = packageJson.get("main"); + if (mainElm instanceof JsonPrimitive && ((JsonPrimitive)mainElm).isString()) { + mainStr = mainElm.getAsString(); + } + + // Look for source files `./src` if it exists + Path sourceDir = packageDir.resolve("src"); + if (Files.isDirectory(sourceDir)) { + // Try `src/index.ts` + resolved = tryResolveTypeScriptOrJavaScriptFile(sourceDir, "index"); + if (resolved != null) { + return resolved; + } + + // If "main" was defined, try to map it to a file in `src`. + // For example `out/dist/foo.bundle.js` might be mapped back to `src/foo.ts`. + if (mainStr != null) { + Path candidatePath = Paths.get(mainStr); + + // Strip off prefix directories that don't exist under `src/`, such as `out` and `dist`. + while (candidatePath.getNameCount() > 1 && !Files.isDirectory(sourceDir.resolve(candidatePath.getParent()))) { + candidatePath = candidatePath.subpath(1, candidatePath.getNameCount()); + } + + // Strip off extensions until a file can be found + while (true) { + resolved = tryResolveTypeScriptOrJavaScriptFile(sourceDir, candidatePath.toString()); + if (resolved != null) { + return resolved; + } + Path withoutExt = candidatePath.resolveSibling(FileUtil.stripExtension(candidatePath.getFileName().toString())); + if (withoutExt.equals(candidatePath)) break; // No more extensions to strip + candidatePath = withoutExt; + } + } + } + + // Try /index.js - this time allowing JS extension. + resolved = tryResolveWithExtensions(packageDir, "index", FileType.JS.getExtensions()); + if (resolved != null) { + return resolved; + } + + return resolved; + } + private ExtractorConfig mkExtractorConfig() { ExtractorConfig config = new ExtractorConfig(true); config = config.withSourceType(getSourceType()); From a220268ad8e5527af8493c9a43249f5f2a7e4887 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 16 Jan 2020 15:56:31 +0000 Subject: [PATCH 035/148] TS: Install deps under scratch dir --- .../extractor/lib/typescript/src/common.ts | 111 ++++++++++++- .../extractor/lib/typescript/src/main.ts | 4 +- .../com/semmle/js/extractor/AutoBuild.java | 156 +++++++++--------- .../DependencyInstallationResult.java | 23 ++- .../js/extractor/EnvironmentVariables.java | 11 ++ .../src/com/semmle/js/extractor/Main.java | 2 +- .../js/extractor/test/AutoBuildTests.java | 5 - .../semmle/js/parser/TypeScriptParser.java | 47 ++++-- 8 files changed, 238 insertions(+), 121 deletions(-) diff --git a/javascript/extractor/lib/typescript/src/common.ts b/javascript/extractor/lib/typescript/src/common.ts index cad531ca622..a125e7b9bc3 100644 --- a/javascript/extractor/lib/typescript/src/common.ts +++ b/javascript/extractor/lib/typescript/src/common.ts @@ -1,10 +1,33 @@ import * as ts from "./typescript"; import { TypeTable } from "./type_table"; +import * as pathlib from "path"; + +/** + * Extracts the package name from the prefix of an import string. + */ +const packageNameRex = /^(?:@[\w.-]+[/\\])?\w[\w.-]*(?=[/\\]|$)/; +const extensions = ['.ts', '.tsx', '.d.ts']; export class Project { public program: ts.Program = null; + private host: ts.CompilerHost; + private resolutionCache: ts.ModuleResolutionCache; + private sourceRoot: string; + /** Directory whose folder structure mirrors the real source root, but with `node_modules` installed. */ + private virtualSourceRoot: string; - constructor(public tsConfig: string, public config: ts.ParsedCommandLine, public typeTable: TypeTable) {} + constructor(public tsConfig: string, public config: ts.ParsedCommandLine, public typeTable: TypeTable, public packageLocations: PackageLocationMap) { + this.resolveModuleNames = this.resolveModuleNames.bind(this); + + this.resolutionCache = ts.createModuleResolutionCache(pathlib.dirname(tsConfig), ts.sys.realpath, config.options); + let host = ts.createCompilerHost(config.options, true); + host.resolveModuleNames = this.resolveModuleNames; + host.trace = undefined; // Disable tracing which would otherwise go to standard out + this.host = host; + + this.sourceRoot = process.cwd(); + this.virtualSourceRoot = process.env["CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR"]; + } public unload(): void { this.typeTable.releaseProgram(); @@ -12,9 +35,8 @@ export class Project { } public load(): void { - let host = ts.createCompilerHost(this.config.options, true); - host.trace = undefined; // Disable tracing which would otherwise go to standard out - this.program = ts.createProgram(this.config.fileNames, this.config.options, host); + const { config, host } = this; + this.program = ts.createProgram(config.fileNames, config.options, host); this.typeTable.setProgram(this.program); } @@ -27,4 +49,85 @@ export class Project { this.unload(); this.load(); } + + /** + * Override for module resolution in the TypeScript compiler host. + */ + private resolveModuleNames( + moduleNames: string[], + containingFile: string, + reusedNames: string[], + redirectedReference: ts.ResolvedProjectReference, + options: ts.CompilerOptions) { + + const { host, resolutionCache } = this; + return moduleNames.map((moduleName) => { + let redirected = this.redirectModuleName(moduleName, containingFile, options); + if (redirected != null) return redirected; + return ts.resolveModuleName(moduleName, containingFile, options, host, resolutionCache).resolvedModule; + }); + } + + /** + * Returns the path that the given import string should be redirected to, or null if it should + * fall back to standard module resolution. + */ + private redirectModuleName(moduleName: string, containingFile: string, options: ts.CompilerOptions): ts.ResolvedModule { + // Get a package name from the leading part of the module name, e.g. '@scope/foo' from '@scope/foo/bar'. + let packageNameMatch = packageNameRex.exec(moduleName); + if (packageNameMatch == null) return null; + let packageName = packageNameMatch[0]; + + // Get the overridden location of this package, if one exists. + let packageEntryPoint = this.packageLocations.get(packageName); + if (packageEntryPoint == null) { + // The package is not overridden, but we have established that it begins with a valid package name. + // Do a lookup in the virtual source root (where dependencies are installed) by changing the 'containing file'. + let virtualContainingFile = this.toVirtualPath(containingFile); + if (virtualContainingFile != null) { + return ts.resolveModuleName(moduleName, virtualContainingFile, options, this.host, this.resolutionCache).resolvedModule; + } + return null; + } + + // If the requested module name is exactly the overridden package name, + // return the entry point file (it is not necessarily called `index.ts`). + if (moduleName.length === packageName.length) { + return { resolvedFileName: packageEntryPoint, isExternalLibraryImport: true }; + } + + // Get the suffix after the package name, e.g. the '/bar' in '@scope/foo/bar'. + let suffix = moduleName.substring(packageName.length); + + // Resolve the suffix relative to the package directory. + let packageDir = pathlib.dirname(packageEntryPoint); + let joinedPath = pathlib.join(packageDir, suffix); + + // Add implicit '/index' + if (ts.sys.directoryExists(joinedPath)) { + joinedPath = pathlib.join(joinedPath, 'index'); + } + + // Try each recognized extension. We must not return a file whose extension is not + // recognized by TypeScript. + for (let ext of extensions) { + let candidate = joinedPath.endsWith(ext) ? joinedPath : (joinedPath + ext); + if (ts.sys.fileExists(candidate)) { + return { resolvedFileName: candidate, isExternalLibraryImport: true }; + } + } + + return null; + } + + /** + * Maps a path under the real source root to the corresonding path in the virtual source root. + */ + private toVirtualPath(path: string) { + let relative = pathlib.relative(this.sourceRoot, path); + if (relative.startsWith('..') || pathlib.isAbsolute(relative)) return null; + return pathlib.join(this.virtualSourceRoot, relative); + } } + +export type PackageLocationMap = Map; diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index 0a2da694869..3a488456f55 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -47,6 +47,7 @@ interface ParseCommand { interface OpenProjectCommand { command: "open-project"; tsConfig: string; + packageLocations: [string, string][]; } interface CloseProjectCommand { command: "close-project"; @@ -255,7 +256,7 @@ function handleOpenProjectCommand(command: OpenProjectCommand) { readFile: ts.sys.readFile, }; let config = ts.parseJsonConfigFileContent(tsConfig.config, parseConfigHost, basePath); - let project = new Project(tsConfigFilename, config, state.typeTable); + let project = new Project(tsConfigFilename, config, state.typeTable, new Map(command.packageLocations)); project.load(); state.project = project; @@ -529,6 +530,7 @@ if (process.argv.length > 2) { handleOpenProjectCommand({ command: "open-project", tsConfig: argument, + packageLocations: [], }); for (let sf of state.project.program.getSourceFiles()) { if (pathlib.basename(sf.fileName) === "lib.d.ts") continue; diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index 7d89f8edf88..dd04dc1c8f9 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -17,7 +17,6 @@ import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; -import java.nio.file.StandardCopyOption; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Arrays; @@ -29,7 +28,6 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; import java.util.stream.Stream; import com.google.gson.Gson; @@ -204,6 +202,7 @@ public class AutoBuild { private final Set xmlExtensions = new LinkedHashSet<>(); private ProjectLayout filters; private final Path LGTM_SRC, SEMMLE_DIST; + private final Path scratchDir; private final TypeScriptMode typeScriptMode; private final String defaultEncoding; private ExecutorService threadPool; @@ -217,6 +216,7 @@ public class AutoBuild { public AutoBuild() { this.LGTM_SRC = toRealPath(getPathFromEnvVar("LGTM_SRC")); this.SEMMLE_DIST = Paths.get(EnvironmentVariables.getExtractorRoot()); + this.scratchDir = Paths.get(EnvironmentVariables.getScratchDir()); this.outputConfig = new ExtractorOutputConfig(LegacyLanguage.JAVASCRIPT); this.trapCache = mkTrapCache(); this.typeScriptMode = @@ -563,12 +563,9 @@ public class AutoBuild { } // extract TypeScript projects and files - Set extractedFiles; - try { - extractedFiles = extractTypeScript(defaultExtractor, filesToExtract, tsconfigFiles); - } finally { - restoreOriginalFiles(dependencyInstallationResult.getOriginalFiles()); - } + Set extractedFiles = + extractTypeScript( + defaultExtractor, filesToExtract, tsconfigFiles, dependencyInstallationResult); // extract remaining files for (Path f : filesToExtract) { @@ -605,19 +602,6 @@ public class AutoBuild { } } - protected void restoreOriginalFiles(Map originalFiles) { - originalFiles.forEach( - (file, original) -> { - try { - Files.move(original, file, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - throw new ResourceError("Could not restore original file: " + file, e); - } - }); - } - - private static final Pattern validPackageName = Pattern.compile("(@[\\w.-]+/)?\\w[\\w.-]*"); - private static Path tryResolveWithExtensions(Path dir, String stem, Iterable extensions) { for (String ext : extensions) { Path path = dir.resolve(stem + ext); @@ -633,18 +617,27 @@ public class AutoBuild { if (resolved != null) return resolved; return tryResolveWithExtensions(dir, stem, FileType.JS.getExtensions()); } - + + private String getChildAsString(JsonObject obj, String name) { + JsonElement child = obj.get(name); + if (child instanceof JsonPrimitive && ((JsonPrimitive)child).isString()) { + return child.getAsString(); + } + return null; + } protected DependencyInstallationResult installDependencies(Set filesToExtract) { if (!verifyYarnInstallation()) { return DependencyInstallationResult.empty; } + + final Path sourceRoot = Paths.get(".").toAbsolutePath(); + final Path virtualSourceRoot = this.scratchDir.toAbsolutePath(); - Path rootNodeModules = Paths.get("node_modules"); - - // Read all package.json files, and install symlinks to them. + // Read all package.json files and index them by name. Map packageJsonFiles = new LinkedHashMap<>(); - Set packagesInRepo = new LinkedHashSet<>(); + Map packagesInRepo = new LinkedHashMap<>(); + Map packageMainFile = new LinkedHashMap<>(); for (Path file : filesToExtract) { if (file.getFileName().toString().equals("package.json")) { try { @@ -655,26 +648,9 @@ public class AutoBuild { file = file.toAbsolutePath(); packageJsonFiles.put(file, jsonObject); - JsonElement nameElm = jsonObject.get("name"); - if (nameElm instanceof JsonPrimitive && ((JsonPrimitive) nameElm).isString()) { - String name = nameElm.getAsString(); - packagesInRepo.add(name); - - if (validPackageName.matcher(name).matches()) { - // Create a symlink to the package: /node_modules/foo -> /path/to/foo - try { - Path symlinkPath = rootNodeModules.resolve(name); - if (!Files.exists(symlinkPath)) { - Files.createDirectories(symlinkPath.getParent()); - Files.createSymbolicLink(symlinkPath, file.getParent()); - } else { - // If node_modules/foo already exists, presumably it contains the right thing, - // so just continue extraction with that in place. - } - } catch (IOException e) { - throw new ResourceError("Could not install symlink to package " + file, e); - } - } + String name = getChildAsString(jsonObject, "name"); + if (name != null) { + packagesInRepo.put(name, file); } } catch (JsonParseException e) { System.err.println("Could not parse JSON file: " + file); @@ -684,59 +660,75 @@ public class AutoBuild { } } - // Remove all dependencies on local packages from package.json files so yarn doesn't - // try to download them. Yarn would fail otherwise if these packages were never published. - // These packages will instead be found through the symlink we installed in node_modules above. + // Process all package.json files now that we know the names of all local packages. + // - remove dependencies on local packages + // - guess the main file for each package // Note that we ignore optional dependencies during installation, so "optionalDependencies" // is ignored here as well. final List dependencyFields = Arrays.asList("dependencies", "devDependencies", "peerDependencies"); - final Set filesToChange = new LinkedHashSet<>(); packageJsonFiles.forEach( (path, packageJson) -> { + Path relativePath = sourceRoot.relativize(path); for (String dependencyField : dependencyFields) { JsonElement dependencyElm = packageJson.get(dependencyField); if (!(dependencyElm instanceof JsonObject)) continue; JsonObject dependencyObj = (JsonObject) dependencyElm; - for (String packageName : packagesInRepo) { - if (!dependencyObj.has(packageName)) continue; - dependencyObj.remove(packageName); - filesToChange.add(path); + List propsToRemove = new ArrayList<>(); + for (String packageName : dependencyObj.keySet()) { + if (packagesInRepo.containsKey(packageName)) { + // Remove dependency on local package + propsToRemove.add(packageName); + } else { + // Remove file dependency on a package that don't exist in the checkout. + String dependecy = getChildAsString(dependencyObj, packageName); + if (dependecy != null && (dependecy.startsWith("file:") || dependecy.startsWith("./") || dependecy.startsWith("../"))) { + if (dependecy.startsWith("file:")) { + dependecy = dependecy.substring("file:".length()); + } + Path resolvedPackage = path.getParent().resolve(dependecy + "/package.json"); + if (!Files.exists(resolvedPackage)) { + propsToRemove.add(packageName); + } + } + } + } + for (String prop : propsToRemove) { + dependencyObj.remove(prop); } } - // Override "main" to point at the source folder instead of the output directory. - Path entryPoint = guessPackageMainFile(path, packageJson); - if (entryPoint != null) { - System.out.println("Main file for " + path + " set to " + entryPoint); - packageJson.addProperty("main", entryPoint.toString()); - packageJson.remove("typings"); - filesToChange.add(path); - } else { - System.out.println("No main file found for " + path); + // For named packages, find the main file. + String name = getChildAsString(packageJson, "name"); + if (name != null) { + Path entryPoint = guessPackageMainFile(path, packageJson); + if (entryPoint != null) { + System.out.println(relativePath + ": Main file set to " + sourceRoot.relativize(entryPoint)); + packageMainFile.put(name, entryPoint); + } else { + System.out.println(relativePath + ": Main file not found"); + } } }); // Write the new package.json files to disk - Map originalFiles = new LinkedHashMap<>(); - final String backupFilename = "package.json.lgtm.backup"; - for (Path file : filesToChange) { - Path backup = file.resolveSibling(backupFilename); + for (Path file : packageJsonFiles.keySet()) { + Path relativePath = sourceRoot.relativize(file); + Path virtualFile = virtualSourceRoot.resolve(relativePath); + try { - originalFiles.put(file, backup); - Files.move(file, backup, StandardCopyOption.REPLACE_EXISTING); + Files.createDirectories(virtualFile.getParent()); + try (Writer writer = Files.newBufferedWriter(virtualFile)) { + new Gson().toJson(packageJsonFiles.get(file), writer); + } } catch (IOException e) { - throw new ResourceError("Could not backup package.json file: " + file, e); - } - try (Writer writer = Files.newBufferedWriter(file)) { - new Gson().toJson(packageJsonFiles.get(file), writer); - } catch (IOException e) { - throw new ResourceError("Could not rewrite package.json file: " + file, e); + throw new ResourceError("Could not rewrite package.json file: " + virtualFile, e); } } // Install dependencies for (Path file : packageJsonFiles.keySet()) { - System.out.println("Installing dependencies from " + file); + Path virtualFile = virtualSourceRoot.resolve(sourceRoot.relativize(file)); + System.out.println("Installing dependencies from " + virtualFile); ProcessBuilder pb = new ProcessBuilder( Arrays.asList( @@ -750,18 +742,17 @@ public class AutoBuild { "--no-default-rc", "--no-bin-links", "--pure-lockfile")); - pb.directory(file.getParent().toFile()); + pb.directory(virtualFile.getParent().toFile()); pb.redirectOutput(Redirect.INHERIT); pb.redirectError(Redirect.INHERIT); try { pb.start().waitFor(this.installDependenciesTimeout, TimeUnit.MILLISECONDS); } catch (IOException | InterruptedException ex) { - restoreOriginalFiles(originalFiles); // Try to clean up before giving up. throw new ResourceError("Could not install dependencies from " + file, ex); } } - return new DependencyInstallationResult(originalFiles); + return new DependencyInstallationResult(packageMainFile); } /** @@ -838,7 +829,10 @@ public class AutoBuild { } private Set extractTypeScript( - FileExtractor extractor, Set files, List tsconfig) { + FileExtractor extractor, + Set files, + List tsconfig, + DependencyInstallationResult deps) { Set extractedFiles = new LinkedHashSet<>(); if (hasTypeScriptFiles(files) || !tsconfig.isEmpty()) { @@ -850,7 +844,7 @@ public class AutoBuild { for (Path projectPath : tsconfig) { File projectFile = projectPath.toFile(); long start = logBeginProcess("Opening project " + projectFile); - ParsedProject project = tsParser.openProject(projectFile); + ParsedProject project = tsParser.openProject(projectFile, deps); logEndProcess(start, "Done opening project " + projectFile); // Extract all files belonging to this project which are also matched // by our include/exclude filters. diff --git a/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java b/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java index 74b3d4e459d..27b34369f04 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java +++ b/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java @@ -4,23 +4,22 @@ import java.nio.file.Path; import java.util.Collections; import java.util.Map; -/** - * Contains the results of installing dependencies. - */ +/** Contains the results of installing dependencies. */ public class DependencyInstallationResult { - private Map originalFiles; - - public static final DependencyInstallationResult empty = new DependencyInstallationResult(Collections.emptyMap()); + private Map packageLocations; - public DependencyInstallationResult(Map originalFiles) { - this.originalFiles = originalFiles; + public static final DependencyInstallationResult empty = + new DependencyInstallationResult(Collections.emptyMap()); + + public DependencyInstallationResult(Map localPackages) { + this.packageLocations = localPackages; } /** - * Returns the mapping from files left behind by dependency installation to - * the backups of those files, to be restored after extraction. + * Returns the mapping from package names to the TypeScript file that should + * act as its main entry point. */ - public Map getOriginalFiles() { - return originalFiles; + public Map getPackageLocations() { + return packageLocations; } } diff --git a/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java b/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java index 4f991c42fa7..7c5bac5a9f5 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java +++ b/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java @@ -7,6 +7,9 @@ import com.semmle.util.process.Env.Var; public class EnvironmentVariables { public static final String CODEQL_EXTRACTOR_JAVASCRIPT_ROOT_ENV_VAR = "CODEQL_EXTRACTOR_JAVASCRIPT_ROOT"; + + public static final String CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR_ENV_VAR = + "CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR"; /** * Gets the extractor root based on the CODEQL_EXTRACTOR_JAVASCRIPT_ROOT or @@ -31,4 +34,12 @@ public class EnvironmentVariables { } return env; } + + public static String getScratchDir() { + String env = Env.systemEnv().get(CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR_ENV_VAR); + if (env == null) { + throw new UserError(CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR_ENV_VAR + " must be set"); + } + return env; + } } diff --git a/javascript/extractor/src/com/semmle/js/extractor/Main.java b/javascript/extractor/src/com/semmle/js/extractor/Main.java index 4f522513873..062d44de30c 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/Main.java +++ b/javascript/extractor/src/com/semmle/js/extractor/Main.java @@ -140,7 +140,7 @@ public class Main { for (File projectFile : projectFiles) { long start = verboseLogStartTimer(ap, "Opening project " + projectFile); - ParsedProject project = tsParser.openProject(projectFile); + ParsedProject project = tsParser.openProject(projectFile, DependencyInstallationResult.empty); verboseLogEndTimer(ap, start); // Extract all files belonging to this project which are also matched // by our include/exclude filters. diff --git a/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java b/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java index dfe9c789197..aefe68f7370 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java +++ b/javascript/extractor/src/com/semmle/js/extractor/test/AutoBuildTests.java @@ -131,11 +131,6 @@ public class AutoBuildTests { } } - @Override - protected void restoreOriginalFiles(java.util.Map originalFiles) { - // Do nothing - } - @Override protected DependencyInstallationResult installDependencies(Set filesToExtract) { // never install dependencies during testing diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java index 93a241b1e3d..c842c6e39ba 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java @@ -1,12 +1,28 @@ package com.semmle.js.parser; -import ch.qos.logback.classic.Level; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.lang.ProcessBuilder.Redirect; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; +import com.semmle.js.extractor.DependencyInstallationResult; import com.semmle.js.extractor.EnvironmentVariables; import com.semmle.js.extractor.ExtractionMetrics; import com.semmle.js.parser.JSParser.Result; @@ -23,21 +39,8 @@ import com.semmle.util.logging.LogbackUtils; import com.semmle.util.process.AbstractProcessBuilder; import com.semmle.util.process.Builder; import com.semmle.util.process.Env; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.Closeable; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.lang.ProcessBuilder.Redirect; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; + +import ch.qos.logback.classic.Level; /** * The Java half of our wrapper for invoking the TypeScript parser. @@ -409,10 +412,20 @@ public class TypeScriptParser { * *

    Only one project should be opened at once. */ - public ParsedProject openProject(File tsConfigFile) { + public ParsedProject openProject(File tsConfigFile, DependencyInstallationResult deps) { JsonObject request = new JsonObject(); request.add("command", new JsonPrimitive("open-project")); request.add("tsConfig", new JsonPrimitive(tsConfigFile.getPath())); + JsonArray packageLocations = new JsonArray(); + deps.getPackageLocations() + .forEach( + (packageName, packageDir) -> { + JsonArray entry = new JsonArray(); + entry.add(packageName); + entry.add(packageDir.toString()); + packageLocations.add(entry); + }); + request.add("packageLocations", packageLocations); JsonObject response = talkToParserWrapper(request); try { checkResponseType(response, "project-opened"); From 5719b44fa567cd9e532f3c37435c76dd989cdc6f Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Wed, 22 Jan 2020 11:43:33 +0000 Subject: [PATCH 036/148] TS: Add some documentation --- .../com/semmle/js/extractor/AutoBuild.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index dd04dc1c8f9..417e40a2e7a 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -602,6 +602,10 @@ public class AutoBuild { } } + /** + * Returns an existing file named dir/stem.ext where ext is any + * of the given extensions, or null if no such file exists. + */ private static Path tryResolveWithExtensions(Path dir, String stem, Iterable extensions) { for (String ext : extensions) { Path path = dir.resolve(stem + ext); @@ -612,12 +616,19 @@ public class AutoBuild { return null; } + /** + * Returns an existing file named dir/stem.ext where ext is any TypeScript or JavaScript extension, + * or null if no such file exists. + */ private static Path tryResolveTypeScriptOrJavaScriptFile(Path dir, String stem) { Path resolved = tryResolveWithExtensions(dir, stem, FileType.TYPESCRIPT.getExtensions()); if (resolved != null) return resolved; return tryResolveWithExtensions(dir, stem, FileType.JS.getExtensions()); } + /** + * Gets a child of a JSON object as a string, or null. + */ private String getChildAsString(JsonObject obj, String name) { JsonElement child = obj.get(name); if (child instanceof JsonPrimitive && ((JsonPrimitive)child).isString()) { @@ -626,6 +637,25 @@ public class AutoBuild { return null; } + /** + * Installs dependencies for use by the TypeScript type checker. + *

    + * Some packages must be downloaded while others exist within the same repo ("monorepos") + * but are not in a location where TypeScript would look for it. + *

    + * Downloaded packages are intalled under {@link #scratchDir}, in a mirrored directory hierarchy + * we call the "virtual source root". + * Each package.json file is rewritten and copied to the virtual source root, + * where yarn install is invoked. + *

    + * Packages that exists within the repo are stripped from the dependencies + * before installation, so they are not downloaded. Since they are part of the main source tree, + * these packages are not mirrored under the virtual source root. + * Instead, an explicit package location mapping is passed to the TypeScript parser wrapper. + *

    + * The TypeScript parser wrapper then overrides module resolution so packages can be found + * under the virtual source root and via that package location mapping. + */ protected DependencyInstallationResult installDependencies(Set filesToExtract) { if (!verifyYarnInstallation()) { return DependencyInstallationResult.empty; From 7e8fb1428e666bfa901663d6c12591239d8fa60f Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Wed, 22 Jan 2020 15:03:03 +0000 Subject: [PATCH 037/148] TS: Support tsconfig.json extending from ./node_modules --- .../extractor/lib/typescript/src/common.ts | 21 ++----- .../extractor/lib/typescript/src/main.ts | 59 ++++++++++++++++--- .../lib/typescript/src/virtual_source_root.ts | 34 +++++++++++ .../com/semmle/js/extractor/AutoBuild.java | 2 +- .../DependencyInstallationResult.java | 23 ++++++-- .../semmle/js/parser/TypeScriptParser.java | 29 +++++---- 6 files changed, 127 insertions(+), 41 deletions(-) create mode 100644 javascript/extractor/lib/typescript/src/virtual_source_root.ts diff --git a/javascript/extractor/lib/typescript/src/common.ts b/javascript/extractor/lib/typescript/src/common.ts index a125e7b9bc3..729ebf8dcb4 100644 --- a/javascript/extractor/lib/typescript/src/common.ts +++ b/javascript/extractor/lib/typescript/src/common.ts @@ -1,6 +1,7 @@ import * as ts from "./typescript"; import { TypeTable } from "./type_table"; import * as pathlib from "path"; +import { VirtualSourceRoot } from "./virtual_source_root"; /** * Extracts the package name from the prefix of an import string. @@ -12,11 +13,9 @@ export class Project { public program: ts.Program = null; private host: ts.CompilerHost; private resolutionCache: ts.ModuleResolutionCache; - private sourceRoot: string; - /** Directory whose folder structure mirrors the real source root, but with `node_modules` installed. */ - private virtualSourceRoot: string; - constructor(public tsConfig: string, public config: ts.ParsedCommandLine, public typeTable: TypeTable, public packageLocations: PackageLocationMap) { + constructor(public tsConfig: string, public config: ts.ParsedCommandLine, public typeTable: TypeTable, public packageLocations: PackageLocationMap, + public virtualSourceRoot: VirtualSourceRoot) { this.resolveModuleNames = this.resolveModuleNames.bind(this); this.resolutionCache = ts.createModuleResolutionCache(pathlib.dirname(tsConfig), ts.sys.realpath, config.options); @@ -24,9 +23,6 @@ export class Project { host.resolveModuleNames = this.resolveModuleNames; host.trace = undefined; // Disable tracing which would otherwise go to standard out this.host = host; - - this.sourceRoot = process.cwd(); - this.virtualSourceRoot = process.env["CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR"]; } public unload(): void { @@ -83,7 +79,7 @@ export class Project { if (packageEntryPoint == null) { // The package is not overridden, but we have established that it begins with a valid package name. // Do a lookup in the virtual source root (where dependencies are installed) by changing the 'containing file'. - let virtualContainingFile = this.toVirtualPath(containingFile); + let virtualContainingFile = this.virtualSourceRoot.toVirtualPath(containingFile); if (virtualContainingFile != null) { return ts.resolveModuleName(moduleName, virtualContainingFile, options, this.host, this.resolutionCache).resolvedModule; } @@ -119,15 +115,6 @@ export class Project { return null; } - - /** - * Maps a path under the real source root to the corresonding path in the virtual source root. - */ - private toVirtualPath(path: string) { - let relative = pathlib.relative(this.sourceRoot, path); - if (relative.startsWith('..') || pathlib.isAbsolute(relative)) return null; - return pathlib.join(this.virtualSourceRoot, relative); - } } export type PackageLocationMap = Map; diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index 3a488456f55..750d1699f1c 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -37,8 +37,9 @@ import * as readline from "readline"; import * as ts from "./typescript"; import * as ast_extractor from "./ast_extractor"; -import { Project } from "./common"; +import { Project, PackageLocationMap } from "./common"; import { TypeTable } from "./type_table"; +import { VirtualSourceRoot } from "./virtual_source_root"; interface ParseCommand { command: "parse"; @@ -47,7 +48,8 @@ interface ParseCommand { interface OpenProjectCommand { command: "open-project"; tsConfig: string; - packageLocations: [string, string][]; + packageEntryPoints: [string, string][]; + packageJsonFiles: [string, string][]; } interface CloseProjectCommand { command: "close-project"; @@ -243,20 +245,62 @@ function parseSingleFile(filename: string): {ast: ts.SourceFile, code: string} { return {ast, code}; } +const nodeModulesRex = /[/\\]node_modules[/\\]((?:@[\w.-]+[/\\])?\w[\w.-]*)[/\\](.*)/; + function handleOpenProjectCommand(command: OpenProjectCommand) { Error.stackTraceLimit = Infinity; let tsConfigFilename = String(command.tsConfig); let tsConfig = ts.readConfigFile(tsConfigFilename, ts.sys.readFile); let basePath = pathlib.dirname(tsConfigFilename); + let packageEntryPoints = new Map(command.packageEntryPoints); + let packageJsonFiles = new Map(command.packageJsonFiles); + let virtualSourceRoot = new VirtualSourceRoot(process.cwd(), process.env["CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR"]); + + /** + * Rewrites path segments of form `node_modules/PACK/suffix` to be relative to + * the location of package PACK in the source tree, if it exists. + */ + function redirectNodeModulesPath(path: string) { + let nodeModulesMatch = nodeModulesRex.exec(path); + if (nodeModulesMatch == null) return null; + let packageName = nodeModulesMatch[1]; + let packageJsonFile = packageJsonFiles.get(packageName); + if (packageJsonFile == null) return null; + let packageDir = pathlib.dirname(packageJsonFile); + let suffix = nodeModulesMatch[2]; + let finalPath = pathlib.join(packageDir, suffix); + if (!ts.sys.fileExists(finalPath)) return null; + return finalPath; + } + + /** + * Create the host passed to the tsconfig.json parser. + * + * We override its file system access in case there is an "extends" + * clause pointing into "./node_modules", which must be redirected to + * the location of an installed package or a checked-in package. + */ let parseConfigHost: ts.ParseConfigHost = { useCaseSensitiveFileNames: true, - readDirectory: ts.sys.readDirectory, - fileExists: (path: string) => fs.existsSync(path), - readFile: ts.sys.readFile, + readDirectory: ts.sys.readDirectory, // No need to override traversal/glob matching + fileExists: (path: string) => { + return ts.sys.fileExists(path) + || virtualSourceRoot.toVirtualPathIfFileExists(path) != null + || redirectNodeModulesPath(path) != null; + }, + readFile: (path: string) => { + if (!fs.existsSync(path)) { + let virtualPath = virtualSourceRoot.toVirtualPathIfFileExists(path); + if (virtualPath != null) return ts.sys.readFile(virtualPath); + virtualPath = redirectNodeModulesPath(path); + if (virtualPath != null) return ts.sys.readFile(virtualPath); + } + return ts.sys.readFile(path); + } }; let config = ts.parseJsonConfigFileContent(tsConfig.config, parseConfigHost, basePath); - let project = new Project(tsConfigFilename, config, state.typeTable, new Map(command.packageLocations)); + let project = new Project(tsConfigFilename, config, state.typeTable, packageEntryPoints, virtualSourceRoot); project.load(); state.project = project; @@ -530,7 +574,8 @@ if (process.argv.length > 2) { handleOpenProjectCommand({ command: "open-project", tsConfig: argument, - packageLocations: [], + packageEntryPoints: [], + packageJsonFiles: [], }); for (let sf of state.project.program.getSourceFiles()) { if (pathlib.basename(sf.fileName) === "lib.d.ts") continue; diff --git a/javascript/extractor/lib/typescript/src/virtual_source_root.ts b/javascript/extractor/lib/typescript/src/virtual_source_root.ts new file mode 100644 index 00000000000..ef7e481d666 --- /dev/null +++ b/javascript/extractor/lib/typescript/src/virtual_source_root.ts @@ -0,0 +1,34 @@ +import * as pathlib from "path"; +import * as ts from "./typescript"; + +/** + * Mapping from the source root to the virtual source root. + */ +export class VirtualSourceRoot { + constructor( + private sourceRoot: string, + + /** Directory whose folder structure mirrors the real source root, but with `node_modules` installed. */ + private virtualSourceRoot: string, + ) {} + + /** + * Maps a path under the real source root to the corresonding path in the virtual source root. + */ + public toVirtualPath(path: string) { + let relative = pathlib.relative(this.sourceRoot, path); + if (relative.startsWith('..') || pathlib.isAbsolute(relative)) return null; + return pathlib.join(this.virtualSourceRoot, relative); + } + + /** + * Maps a path under the real source root to the corresonding path in the virtual source root. + */ + public toVirtualPathIfFileExists(path: string) { + let virtualPath = this.toVirtualPath(path); + if (virtualPath != null && ts.sys.fileExists(virtualPath)) { + return virtualPath; + } + return null; + } +} diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index 417e40a2e7a..1db32a43fcc 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -782,7 +782,7 @@ public class AutoBuild { } } - return new DependencyInstallationResult(packageMainFile); + return new DependencyInstallationResult(packageMainFile, packagesInRepo); } /** diff --git a/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java b/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java index 27b34369f04..5dd6bd60b6a 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java +++ b/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java @@ -6,20 +6,31 @@ import java.util.Map; /** Contains the results of installing dependencies. */ public class DependencyInstallationResult { - private Map packageLocations; + private Map packageEntryPoints; + private Map packageJsonFiles; public static final DependencyInstallationResult empty = - new DependencyInstallationResult(Collections.emptyMap()); + new DependencyInstallationResult(Collections.emptyMap(), Collections.emptyMap()); - public DependencyInstallationResult(Map localPackages) { - this.packageLocations = localPackages; + public DependencyInstallationResult( + Map packageEntryPoints, + Map packageJsonFiles) { + this.packageEntryPoints = packageEntryPoints; + this.packageJsonFiles = packageJsonFiles; } /** * Returns the mapping from package names to the TypeScript file that should * act as its main entry point. */ - public Map getPackageLocations() { - return packageLocations; + public Map getPackageEntryPoints() { + return packageEntryPoints; + } + + /** + * Returns the mapping from package name to corresponding package.json. + */ + public Map getPackageJsonFiles() { + return packageJsonFiles; } } diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java index c842c6e39ba..27ed0e720fb 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java @@ -11,10 +11,12 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.lang.ProcessBuilder.Redirect; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Map; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -404,6 +406,21 @@ public class TypeScriptParser { checkResponseType(response, "ok"); } + /** + * Converts a map to an array of [key, value] pairs. + */ + private JsonArray mapToArray(Map map) { + JsonArray result = new JsonArray(); + map.forEach( + (key, path) -> { + JsonArray entry = new JsonArray(); + entry.add(key); + entry.add(path.toString()); + result.add(entry); + }); + return result; + } + /** * Opens a new project based on a tsconfig.json file. The compiler will analyze all files in the * project. @@ -416,16 +433,8 @@ public class TypeScriptParser { JsonObject request = new JsonObject(); request.add("command", new JsonPrimitive("open-project")); request.add("tsConfig", new JsonPrimitive(tsConfigFile.getPath())); - JsonArray packageLocations = new JsonArray(); - deps.getPackageLocations() - .forEach( - (packageName, packageDir) -> { - JsonArray entry = new JsonArray(); - entry.add(packageName); - entry.add(packageDir.toString()); - packageLocations.add(entry); - }); - request.add("packageLocations", packageLocations); + request.add("packageEntryPoints", mapToArray(deps.getPackageEntryPoints())); + request.add("packageJsonFiles", mapToArray(deps.getPackageJsonFiles())); JsonObject response = talkToParserWrapper(request); try { checkResponseType(response, "project-opened"); From bed6a9886f080b5a2f99af2c2c84d3d398fbc4bb Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Wed, 22 Jan 2020 21:42:47 +0100 Subject: [PATCH 038/148] Query to detect LDAP injections in Java Autoformat --- java/ql/src/Security/CWE/CWE-90/LdapInjection.ql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql b/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql index be247b58e57..5ecff4a55b2 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql @@ -15,8 +15,7 @@ import semmle.code.java.dataflow.FlowSources import LdapInjectionLib import DataFlow::PathGraph -from - DataFlow::PathNode source, DataFlow::PathNode sink, LdapInjectionFlowConfig conf +from DataFlow::PathNode source, DataFlow::PathNode sink, LdapInjectionFlowConfig conf where conf.hasFlowPath(source, sink) select sink.getNode(), source, sink, "LDAP query might include code from $@.", source.getNode(), "this user input" From dc30dcf1f875fdbcb98ac68865a8444c0e46d67a Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 23 Jan 2020 12:39:19 +0000 Subject: [PATCH 039/148] TS: Only require SCRATCH_DIR when installing dependencies --- .../extractor/src/com/semmle/js/extractor/AutoBuild.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index 1db32a43fcc..4de435ea6f2 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -202,7 +202,6 @@ public class AutoBuild { private final Set xmlExtensions = new LinkedHashSet<>(); private ProjectLayout filters; private final Path LGTM_SRC, SEMMLE_DIST; - private final Path scratchDir; private final TypeScriptMode typeScriptMode; private final String defaultEncoding; private ExecutorService threadPool; @@ -216,7 +215,6 @@ public class AutoBuild { public AutoBuild() { this.LGTM_SRC = toRealPath(getPathFromEnvVar("LGTM_SRC")); this.SEMMLE_DIST = Paths.get(EnvironmentVariables.getExtractorRoot()); - this.scratchDir = Paths.get(EnvironmentVariables.getScratchDir()); this.outputConfig = new ExtractorOutputConfig(LegacyLanguage.JAVASCRIPT); this.trapCache = mkTrapCache(); this.typeScriptMode = @@ -643,7 +641,7 @@ public class AutoBuild { * Some packages must be downloaded while others exist within the same repo ("monorepos") * but are not in a location where TypeScript would look for it. *

    - * Downloaded packages are intalled under {@link #scratchDir}, in a mirrored directory hierarchy + * Downloaded packages are intalled under SCRATCH_DIR, in a mirrored directory hierarchy * we call the "virtual source root". * Each package.json file is rewritten and copied to the virtual source root, * where yarn install is invoked. @@ -662,7 +660,7 @@ public class AutoBuild { } final Path sourceRoot = Paths.get(".").toAbsolutePath(); - final Path virtualSourceRoot = this.scratchDir.toAbsolutePath(); + final Path virtualSourceRoot = Paths.get(EnvironmentVariables.getScratchDir()).toAbsolutePath(); // Read all package.json files and index them by name. Map packageJsonFiles = new LinkedHashMap<>(); From 852b90a6c9a876deaf9a5149a548e4eb8c62504e Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 23 Jan 2020 16:13:53 +0000 Subject: [PATCH 040/148] TS: Be compatible with odasa/qltest --- .../extractor/lib/typescript/src/virtual_source_root.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/javascript/extractor/lib/typescript/src/virtual_source_root.ts b/javascript/extractor/lib/typescript/src/virtual_source_root.ts index ef7e481d666..43f01455650 100644 --- a/javascript/extractor/lib/typescript/src/virtual_source_root.ts +++ b/javascript/extractor/lib/typescript/src/virtual_source_root.ts @@ -8,7 +8,10 @@ export class VirtualSourceRoot { constructor( private sourceRoot: string, - /** Directory whose folder structure mirrors the real source root, but with `node_modules` installed. */ + /** + * Directory whose folder structure mirrors the real source root, but with `node_modules` installed, + * or undefined if no virtual source root exists. + */ private virtualSourceRoot: string, ) {} @@ -16,6 +19,7 @@ export class VirtualSourceRoot { * Maps a path under the real source root to the corresonding path in the virtual source root. */ public toVirtualPath(path: string) { + if (!this.virtualSourceRoot) return null; let relative = pathlib.relative(this.sourceRoot, path); if (relative.startsWith('..') || pathlib.isAbsolute(relative)) return null; return pathlib.join(this.virtualSourceRoot, relative); From 968c18d208753d63f3215fb2200dbd4d3ad8a354 Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Thu, 23 Jan 2020 22:51:10 +0100 Subject: [PATCH 041/148] Query to detect LDAP injections in Java Refactoring according to review comments. --- .../src/Security/CWE/CWE-90/LdapInjection.ql | 2 +- .../Security/CWE/CWE-90/LdapInjectionLib.qll | 143 +++++++----------- 2 files changed, 55 insertions(+), 90 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql b/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql index 5ecff4a55b2..6b5b37f1093 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql @@ -10,7 +10,7 @@ * external/cwe/cwe-090 */ -import semmle.code.java.Expr +import java import semmle.code.java.dataflow.FlowSources import LdapInjectionLib import DataFlow::PathGraph diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll index 42b65019d9c..d2131dadb80 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll @@ -6,25 +6,13 @@ import semmle.code.java.frameworks.UnboundId import semmle.code.java.frameworks.SpringLdap import semmle.code.java.frameworks.ApacheLdap -/** Holds if the parameter of `c` at index `paramIndex` is varargs. */ -bindingset[paramIndex] -predicate isVarargs(Callable c, int paramIndex) { - c.isVarargs() and paramIndex >= c.getNumberOfParameters() - 1 -} - -/** A data flow source for unvalidated user input that is used to construct LDAP queries. */ -abstract class LdapInjectionSource extends DataFlow::Node { } - -/** A data flow sink for unvalidated user input that is used to construct LDAP queries. */ -abstract class LdapInjectionSink extends DataFlow::ExprNode { } - /** * A taint-tracking configuration for unvalidated user input that is used to construct LDAP queries. */ class LdapInjectionFlowConfig extends TaintTracking::Configuration { LdapInjectionFlowConfig() { this = "LdapInjectionFlowConfig" } - override predicate isSource(DataFlow::Node source) { source instanceof LdapInjectionSource } + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } override predicate isSink(DataFlow::Node sink) { sink instanceof LdapInjectionSink } @@ -56,101 +44,77 @@ class LdapInjectionFlowConfig extends TaintTracking::Configuration { } } -/** A source of remote user input. */ -class RemoteSource extends LdapInjectionSource { - RemoteSource() { this instanceof RemoteFlowSource } -} - -/** A source of local user input. */ -class LocalSource extends LdapInjectionSource { - LocalSource() { this instanceof LocalUserInput } -} - /** * JNDI sink for LDAP injection vulnerabilities, i.e. 1st (DN) or 2nd (filter) argument to * `search` method from `DirContext`. */ -class JndiLdapInjectionSink extends LdapInjectionSink { - JndiLdapInjectionSink() { - exists(MethodAccess ma, Method m, int index | - ma.getMethod() = m and - ma.getArgument(index) = this.getExpr() - | - m.getDeclaringType().getAnAncestor() instanceof TypeDirContext and - m.hasName("search") and - index in [0 .. 1] - ) - } +predicate jndiLdapInjectionSinkMethod(Method m, int index) { + m.getDeclaringType().getAnAncestor() instanceof TypeDirContext and + m.hasName("search") and + index in [0 .. 1] } /** * UnboundID sink for LDAP injection vulnerabilities, * i.e. LDAPConnection.search, LDAPConnection.asyncSearch or LDAPConnection.searchForEntry method. */ -class UnboundIdLdapInjectionSink extends LdapInjectionSink { - UnboundIdLdapInjectionSink() { - exists(MethodAccess ma, Method m, int index, Parameter param | - ma.getMethod() = m and - ma.getArgument(index) = this.getExpr() and - m.getParameter(index) = param - | - // LDAPConnection.search, LDAPConnection.asyncSearch or LDAPConnection.searchForEntry method - ( - m instanceof MethodUnboundIdLDAPConnectionSearch or - m instanceof MethodUnboundIdLDAPConnectionAsyncSearch or - m instanceof MethodUnboundIdLDAPConnectionSearchForEntry - ) and - // Parameter is not varargs - not isVarargs(m, index) - ) - } +predicate unboundIdLdapInjectionSinkMethod(Method m, int index) { + exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() | + m instanceof MethodUnboundIdLDAPConnectionSearch or + m instanceof MethodUnboundIdLDAPConnectionAsyncSearch or + m instanceof MethodUnboundIdLDAPConnectionSearchForEntry + ) } /** * Spring LDAP sink for LDAP injection vulnerabilities, * i.e. LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method. */ -class SpringLdapInjectionSink extends LdapInjectionSink { - SpringLdapInjectionSink() { - exists(MethodAccess ma, Method m, int index, RefType paramType | - ma.getMethod() = m and - ma.getArgument(index) = this.getExpr() and - m.getParameterType(index) = paramType - | - // LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method - ( - m instanceof MethodSpringLdapTemplateAuthenticate or - m instanceof MethodSpringLdapTemplateFind or - m instanceof MethodSpringLdapTemplateFindOne or - m instanceof MethodSpringLdapTemplateSearch or - m instanceof MethodSpringLdapTemplateSearchForContext or - m instanceof MethodSpringLdapTemplateSearchForObject - ) and - ( - // Parameter index is 1 (DN or query) or 2 (filter) if method is not authenticate - index in [0 .. 1] and - not m instanceof MethodSpringLdapTemplateAuthenticate - or - // But it's not the last parameter in case of authenticate method (last param is password) - index in [0 .. 1] and - index < m.getNumberOfParameters() - 1 and - m instanceof MethodSpringLdapTemplateAuthenticate - ) - ) - } +predicate springLdapInjectionSinkMethod(Method m, int index) { + // LdapTemplate.authenticate, LdapTemplate.find* or LdapTemplate.search* method + ( + m instanceof MethodSpringLdapTemplateAuthenticate or + m instanceof MethodSpringLdapTemplateFind or + m instanceof MethodSpringLdapTemplateFindOne or + m instanceof MethodSpringLdapTemplateSearch or + m instanceof MethodSpringLdapTemplateSearchForContext or + m instanceof MethodSpringLdapTemplateSearchForObject + ) and + ( + // Parameter index is 1 (DN or query) or 2 (filter) if method is not authenticate + index in [0 .. 1] and + not m instanceof MethodSpringLdapTemplateAuthenticate + or + // But it's not the last parameter in case of authenticate method (last param is password) + index in [0 .. 1] and + index < m.getNumberOfParameters() - 1 and + m instanceof MethodSpringLdapTemplateAuthenticate + ) } /** Apache LDAP API sink for LDAP injection vulnerabilities, i.e. LdapConnection.search method. */ -class ApacheLdapInjectionSink extends LdapInjectionSink { - ApacheLdapInjectionSink() { - exists(MethodAccess ma, Method m, int index, RefType paramType | +predicate apacheLdapInjectionSinkMethod(Method m, int index) { + exists(Parameter param | m.getParameter(index) = param and not param.isVarargs() | + m.getDeclaringType().getAnAncestor() instanceof TypeApacheLdapConnection and + m.hasName("search") + ) +} + +/** Holds if parameter at index `index` in method `m` is LDAP injection sink. */ +predicate ldapInjectionSinkMethod(Method m, int index) { + jndiLdapInjectionSinkMethod(m, index) or + unboundIdLdapInjectionSinkMethod(m, index) or + springLdapInjectionSinkMethod(m, index) or + apacheLdapInjectionSinkMethod(m, index) +} + +/** A data flow sink for unvalidated user input that is used to construct LDAP queries. */ +class LdapInjectionSink extends DataFlow::ExprNode { + LdapInjectionSink() { + exists(MethodAccess ma, Method m, int index | ma.getMethod() = m and ma.getArgument(index) = this.getExpr() and - m.getParameterType(index) = paramType - | - m.getDeclaringType().getAnAncestor() instanceof TypeApacheLdapConnection and - m.hasName("search") and - not isVarargs(m, index) + ldapInjectionSinkMethod(m, index) ) } } @@ -235,12 +199,13 @@ predicate filterToStringStep(ExprNode n1, ExprNode n2) { * `SearchRequest`, i.e. `new SearchRequest(tainted)`. */ predicate unboundIdSearchRequestStep(ExprNode n1, ExprNode n2) { - exists(ConstructorCall cc, int index | + exists(ConstructorCall cc, int index, Parameter param | cc.getConstructedType() instanceof TypeUnboundIdSearchRequest | n1.asExpr() = cc.getArgument(index) and n2.asExpr() = cc and - not isVarargs(cc.getConstructor(), index) + cc.getConstructor().getParameter(index) = param and + not param.isVarargs() ) } From fc04e06456386cfd114dcec05c6d7dd6e5396c44 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 23 Jan 2020 17:03:58 +0000 Subject: [PATCH 042/148] TS: Allow .js extensions in cross package imports --- .../extractor/lib/typescript/src/common.ts | 2 +- .../com/semmle/js/extractor/AutoBuild.java | 19 ++++++++----------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/javascript/extractor/lib/typescript/src/common.ts b/javascript/extractor/lib/typescript/src/common.ts index 729ebf8dcb4..85d7042576a 100644 --- a/javascript/extractor/lib/typescript/src/common.ts +++ b/javascript/extractor/lib/typescript/src/common.ts @@ -7,7 +7,7 @@ import { VirtualSourceRoot } from "./virtual_source_root"; * Extracts the package name from the prefix of an import string. */ const packageNameRex = /^(?:@[\w.-]+[/\\])?\w[\w.-]*(?=[/\\]|$)/; -const extensions = ['.ts', '.tsx', '.d.ts']; +const extensions = ['.ts', '.tsx', '.d.ts', '.js', '.jsx']; export class Project { public program: ts.Program = null; diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index 4de435ea6f2..9850ac3211e 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -728,7 +728,11 @@ public class AutoBuild { // For named packages, find the main file. String name = getChildAsString(packageJson, "name"); if (name != null) { - Path entryPoint = guessPackageMainFile(path, packageJson); + Path entryPoint = guessPackageMainFile(path, packageJson, FileType.TYPESCRIPT.getExtensions()); + if (entryPoint == null) { + // Try a TypeScript-recognized JS extension instead + entryPoint = guessPackageMainFile(path, packageJson, Arrays.asList(".js", ".jsx")); + } if (entryPoint != null) { System.out.println(relativePath + ": Main file set to " + sourceRoot.relativize(entryPoint)); packageMainFile.put(name, entryPoint); @@ -788,12 +792,11 @@ public class AutoBuild { * given package - that is, the file you get when importing the package by name * without any path suffix. */ - private Path guessPackageMainFile(Path packageJsonFile, JsonObject packageJson) { + private Path guessPackageMainFile(Path packageJsonFile, JsonObject packageJson, Iterable extensions) { Path packageDir = packageJsonFile.getParent(); // Try /index.ts. - // Do not allow JavaScript extensions at this point as it might be compiled output (will be attempted later). - Path resolved = tryResolveWithExtensions(packageDir, "index", FileType.TYPESCRIPT.getExtensions()); + Path resolved = tryResolveWithExtensions(packageDir, "index", extensions); if (resolved != null) { return resolved; } @@ -828,7 +831,7 @@ public class AutoBuild { // Strip off extensions until a file can be found while (true) { - resolved = tryResolveTypeScriptOrJavaScriptFile(sourceDir, candidatePath.toString()); + resolved = tryResolveWithExtensions(sourceDir, candidatePath.toString(), extensions); if (resolved != null) { return resolved; } @@ -838,12 +841,6 @@ public class AutoBuild { } } } - - // Try /index.js - this time allowing JS extension. - resolved = tryResolveWithExtensions(packageDir, "index", FileType.JS.getExtensions()); - if (resolved != null) { - return resolved; - } return resolved; } From 542ce816dc06860352ac1d8833594082715cd109 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Fri, 24 Jan 2020 09:49:11 +0000 Subject: [PATCH 043/148] TS: Simplify string equality check --- javascript/extractor/lib/typescript/src/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/extractor/lib/typescript/src/common.ts b/javascript/extractor/lib/typescript/src/common.ts index 85d7042576a..fab4220cb22 100644 --- a/javascript/extractor/lib/typescript/src/common.ts +++ b/javascript/extractor/lib/typescript/src/common.ts @@ -88,7 +88,7 @@ export class Project { // If the requested module name is exactly the overridden package name, // return the entry point file (it is not necessarily called `index.ts`). - if (moduleName.length === packageName.length) { + if (moduleName === packageName) { return { resolvedFileName: packageEntryPoint, isExternalLibraryImport: true }; } From 804aef507fcf9d96b829dbb7c4b47b34fb40845a Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Fri, 24 Jan 2020 09:51:03 +0000 Subject: [PATCH 044/148] TS: Remove unneeded alias PackageLocationMap --- javascript/extractor/lib/typescript/src/common.ts | 11 +++++++---- javascript/extractor/lib/typescript/src/main.ts | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/javascript/extractor/lib/typescript/src/common.ts b/javascript/extractor/lib/typescript/src/common.ts index fab4220cb22..781d61e388b 100644 --- a/javascript/extractor/lib/typescript/src/common.ts +++ b/javascript/extractor/lib/typescript/src/common.ts @@ -14,8 +14,13 @@ export class Project { private host: ts.CompilerHost; private resolutionCache: ts.ModuleResolutionCache; - constructor(public tsConfig: string, public config: ts.ParsedCommandLine, public typeTable: TypeTable, public packageLocations: PackageLocationMap, + constructor( + public tsConfig: string, + public config: ts.ParsedCommandLine, + public typeTable: TypeTable, + public packageEntryPoints: Map, public virtualSourceRoot: VirtualSourceRoot) { + this.resolveModuleNames = this.resolveModuleNames.bind(this); this.resolutionCache = ts.createModuleResolutionCache(pathlib.dirname(tsConfig), ts.sys.realpath, config.options); @@ -75,7 +80,7 @@ export class Project { let packageName = packageNameMatch[0]; // Get the overridden location of this package, if one exists. - let packageEntryPoint = this.packageLocations.get(packageName); + let packageEntryPoint = this.packageEntryPoints.get(packageName); if (packageEntryPoint == null) { // The package is not overridden, but we have established that it begins with a valid package name. // Do a lookup in the virtual source root (where dependencies are installed) by changing the 'containing file'. @@ -116,5 +121,3 @@ export class Project { return null; } } - -export type PackageLocationMap = Map; diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index 750d1699f1c..e9e82ed44ed 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -37,7 +37,7 @@ import * as readline from "readline"; import * as ts from "./typescript"; import * as ast_extractor from "./ast_extractor"; -import { Project, PackageLocationMap } from "./common"; +import { Project } from "./common"; import { TypeTable } from "./type_table"; import { VirtualSourceRoot } from "./virtual_source_root"; From 3ca5a3dbe4fc2e655839ecbbb385f34b46427057 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Fri, 24 Jan 2020 09:55:43 +0000 Subject: [PATCH 045/148] TS: Document nodeModulesRex --- javascript/extractor/lib/typescript/src/main.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index e9e82ed44ed..2bcb9afc1b3 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -245,6 +245,12 @@ function parseSingleFile(filename: string): {ast: ts.SourceFile, code: string} { return {ast, code}; } +/** + * Matches a path segment referencing a package in a node_modules folder, and extracts + * two capture groups: the package name, and the relative path in the package. + * + * For example `lib/node_modules/@foo/bar/src/index.js` extracts the capture groups [`@foo/bar`, `src/index.js`]. + */ const nodeModulesRex = /[/\\]node_modules[/\\]((?:@[\w.-]+[/\\])?\w[\w.-]*)[/\\](.*)/; function handleOpenProjectCommand(command: OpenProjectCommand) { From 5448bffeded39264c8218ef5068b63f3eed0bbe1 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 24 Jan 2020 09:58:27 +0000 Subject: [PATCH 046/148] Update javascript/extractor/lib/typescript/src/main.ts Co-Authored-By: Erik Krogh Kristensen --- javascript/extractor/lib/typescript/src/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index 750d1699f1c..a0d09949aa6 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -290,7 +290,7 @@ function handleOpenProjectCommand(command: OpenProjectCommand) { || redirectNodeModulesPath(path) != null; }, readFile: (path: string) => { - if (!fs.existsSync(path)) { + if (!ts.sys.fileExists(path)) { let virtualPath = virtualSourceRoot.toVirtualPathIfFileExists(path); if (virtualPath != null) return ts.sys.readFile(virtualPath); virtualPath = redirectNodeModulesPath(path); From 1f647223e03aa45ec151b0a60a900d57c3e0a47f Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Fri, 24 Jan 2020 10:02:06 +0000 Subject: [PATCH 047/148] TS: Move definition of mainStr --- .../src/com/semmle/js/extractor/AutoBuild.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index 9850ac3211e..153a4cb27de 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -801,15 +801,6 @@ public class AutoBuild { return resolved; } - // Get the "main" property from the package.json - // This usually refers to the compiled output, such as `./out/foo.js` but may hint as to - // the name of main file ("foo" in this case). - String mainStr = null; - JsonElement mainElm = packageJson.get("main"); - if (mainElm instanceof JsonPrimitive && ((JsonPrimitive)mainElm).isString()) { - mainStr = mainElm.getAsString(); - } - // Look for source files `./src` if it exists Path sourceDir = packageDir.resolve("src"); if (Files.isDirectory(sourceDir)) { @@ -819,6 +810,11 @@ public class AutoBuild { return resolved; } + // Get the "main" property from the package.json + // This usually refers to the compiled output, such as `./out/foo.js` but may hint as to + // the name of main file ("foo" in this case). + String mainStr = getChildAsString(packageJson, "main"); + // If "main" was defined, try to map it to a file in `src`. // For example `out/dist/foo.bundle.js` might be mapped back to `src/foo.ts`. if (mainStr != null) { From 7fa0fea2535890f52e5c017d713a90b734f0c415 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Fri, 24 Jan 2020 10:11:53 +0000 Subject: [PATCH 048/148] TS: Address comments in guessMainFile --- .../com/semmle/js/extractor/AutoBuild.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index 153a4cb27de..b4b949c52c0 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -801,6 +801,11 @@ public class AutoBuild { return resolved; } + // Get the "main" property from the package.json + // This usually refers to the compiled output, such as `./out/foo.js` but may hint as to + // the name of main file ("foo" in this case). + String mainStr = getChildAsString(packageJson, "main"); + // Look for source files `./src` if it exists Path sourceDir = packageDir.resolve("src"); if (Files.isDirectory(sourceDir)) { @@ -810,11 +815,6 @@ public class AutoBuild { return resolved; } - // Get the "main" property from the package.json - // This usually refers to the compiled output, such as `./out/foo.js` but may hint as to - // the name of main file ("foo" in this case). - String mainStr = getChildAsString(packageJson, "main"); - // If "main" was defined, try to map it to a file in `src`. // For example `out/dist/foo.bundle.js` might be mapped back to `src/foo.ts`. if (mainStr != null) { @@ -838,7 +838,17 @@ public class AutoBuild { } } - return resolved; + // Try to resolve main as a sibling of the package.json file, such as "./main.js" -> "./main.ts". + if (mainStr != null) { + Path mainPath = Paths.get(mainStr); + String withoutExt = FileUtil.stripExtension(mainPath.getFileName().toString()); + resolved = tryResolveWithExtensions(packageDir, withoutExt, extensions); + if (resolved != null) { + return resolved; + } + } + + return null; } private ExtractorConfig mkExtractorConfig() { From 959ce3b3559b7b0bd4acedb4d33392544416b543 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 24 Jan 2020 13:46:11 -0800 Subject: [PATCH 049/148] C++: add diff tests for DefaultTaintTracking --- .../security-taint/tainted_diff.expected | 11 +++++ .../dataflow/security-taint/tainted_diff.ql | 17 ++++++++ .../security-taint/tainted_ir.expected | 41 +++++++++++++++++++ .../dataflow/security-taint/tainted_ir.ql | 7 ++++ 4 files changed, 76 insertions(+) create mode 100644 cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected create mode 100644 cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.ql create mode 100644 cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected create mode 100644 cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.ql diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected new file mode 100644 index 00000000000..0202ee895d8 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected @@ -0,0 +1,11 @@ +| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:15:50:24 | envStr_ptr | AST only | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:28:50:40 | & ... | AST only | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:50:29:50:40 | envStrGlobal | AST only | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:2:52:12 | * ... | AST only | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:3:52:12 | envStr_ptr | AST only | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:67:7:67:13 | copying | AST only | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:69:10:69:13 | copy | AST only | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:5:70:10 | call to strcpy | AST only | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:12:70:15 | copy | AST only | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | copy | AST only | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.ql b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.ql new file mode 100644 index 00000000000..f76aac99707 --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.ql @@ -0,0 +1,17 @@ +import semmle.code.cpp.security.TaintTracking as AST +import semmle.code.cpp.ir.dataflow.DefaultTaintTracking as IR +import cpp + +from Expr source, Element tainted, string side +where + AST::taintedIncludingGlobalVars(source, tainted, _) and + not IR::taintedIncludingGlobalVars(source, tainted, _) and + not tainted.getLocation().getFile().getExtension() = "h" and + side = "AST only" + or + IR::taintedIncludingGlobalVars(source, tainted, _) and + not AST::taintedIncludingGlobalVars(source, tainted, _) and + not tainted.getLocation().getFile().getExtension() = "h" and + side = "IR only" + +select source, tainted, side diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected new file mode 100644 index 00000000000..95643564b9f --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected @@ -0,0 +1,41 @@ +| test.cpp:23:23:23:28 | call to getenv | test.cpp:8:24:8:25 | s1 | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:14:23:19 | envStr | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:28 | call to getenv | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:23:23:23:40 | (const char *)... | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:6:25:29 | ! ... | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:12 | call to strcmp | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:7:25:29 | (bool)... | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:25:14:25:19 | envStr | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:6:29:28 | ! ... | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:12 | call to strcmp | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:7:29:28 | (bool)... | | +| test.cpp:23:23:23:28 | call to getenv | test.cpp:29:14:29:19 | envStr | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:8:24:8:25 | s1 | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:14:38:19 | envStr | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:28 | call to getenv | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:40 | (const char *)... | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:14:40:19 | envStr | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:8:24:8:25 | s1 | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:45:13:45:24 | envStrGlobal | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:14:49:19 | envStr | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:28 | call to getenv | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:49:23:49:40 | (const char *)... | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:52:16:52:21 | envStr | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:6:54:35 | ! ... | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:12 | call to strcmp | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:7:54:35 | (bool)... | | +| test.cpp:49:23:49:28 | call to getenv | test.cpp:54:14:54:25 | envStrGlobal | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:10:27:10:27 | s | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:18:60:25 | userName | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:34 | call to getenv | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:47 | (const char *)... | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:25:64:32 | userName | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:11:36:11:37 | s2 | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:17:68:24 | userName | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:33 | call to getenv | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:46 | (const char *)... | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:18:70:25 | userName | | +| test.cpp:75:20:75:25 | call to getenv | test.cpp:15:22:15:25 | nptr | | +| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi | | +| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:25 | call to getenv | | +| test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:45 | (const char *)... | | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.ql b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.ql new file mode 100644 index 00000000000..6d8effe7ffe --- /dev/null +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.ql @@ -0,0 +1,7 @@ +import semmle.code.cpp.ir.dataflow.DefaultTaintTracking + +from Expr source, Element tainted, string globalVar +where + taintedIncludingGlobalVars(source, tainted, globalVar) and + not tainted.getLocation().getFile().getExtension() = "h" +select source, tainted, globalVar From 647b9cdcb03880b69944c6bc7e9aff67ab946bdb Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 27 Jan 2020 15:53:28 +0100 Subject: [PATCH 050/148] Python: Autoformat query --- .../Imports/FromImportOfMutableAttribute.ql | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/python/ql/src/Imports/FromImportOfMutableAttribute.ql b/python/ql/src/Imports/FromImportOfMutableAttribute.ql index e5e7c96985e..e75a432d823 100644 --- a/python/ql/src/Imports/FromImportOfMutableAttribute.ql +++ b/python/ql/src/Imports/FromImportOfMutableAttribute.ql @@ -10,22 +10,25 @@ * @precision medium * @id py/import-of-mutable-attribute */ + import python import semmle.python.filters.Tests from ImportMember im, ModuleObject m, AttrNode store_attr, string name -where im.getModule().(ImportExpr).getImportedModuleName() = m.getName() and -im.getName() = name and -/* Modification must be in a function, so it can occur during lifetime of the import value */ -store_attr.getScope() instanceof Function and -/* variable resulting from import must have a long lifetime */ -not im.getScope() instanceof Function and -store_attr.isStore() and -store_attr.getObject(name).refersTo(m) and -/* Import not in same module as modification. */ -not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and -/* Modification is not in a test */ -not store_attr.getScope().getScope*() instanceof TestScope - -select im, "Importing the value of '" + name + "' from $@ means that any change made to $@ will be not be observed locally.", -m, "module " + m.getName(), store_attr, m.getName() + "." + store_attr.getName() +where + im.getModule().(ImportExpr).getImportedModuleName() = m.getName() and + im.getName() = name and + /* Modification must be in a function, so it can occur during lifetime of the import value */ + store_attr.getScope() instanceof Function and + /* variable resulting from import must have a long lifetime */ + not im.getScope() instanceof Function and + store_attr.isStore() and + store_attr.getObject(name).refersTo(m) and + /* Import not in same module as modification. */ + not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and + /* Modification is not in a test */ + not store_attr.getScope().getScope*() instanceof TestScope +select im, + "Importing the value of '" + name + + "' from $@ means that any change made to $@ will be not be observed locally.", m, + "module " + m.getName(), store_attr, m.getName() + "." + store_attr.getName() From d67577e66ce9c5e8e81c3f7550fbf76d814daadc Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 27 Jan 2020 15:48:44 +0100 Subject: [PATCH 051/148] Python: Modernise import related queries Except for Metrics/Dependencies/ExternalDependenciesSourceLinks.ql, since it is rather tricky :D --- .../ql/src/Imports/FromImportOfMutableAttribute.ql | 6 +++--- python/ql/src/Imports/ModuleImportsItself.ql | 6 +++--- python/ql/src/Imports/UnintentionalImport.ql | 12 ++++++------ python/ql/src/Metrics/DirectImports.ql | 6 +++--- python/ql/src/Metrics/TransitiveImports.ql | 6 +++--- python/ql/src/semmle/python/objects/ObjectAPI.qll | 5 +++++ 6 files changed, 23 insertions(+), 18 deletions(-) diff --git a/python/ql/src/Imports/FromImportOfMutableAttribute.ql b/python/ql/src/Imports/FromImportOfMutableAttribute.ql index e75a432d823..aa66fd6d9b2 100644 --- a/python/ql/src/Imports/FromImportOfMutableAttribute.ql +++ b/python/ql/src/Imports/FromImportOfMutableAttribute.ql @@ -14,16 +14,16 @@ import python import semmle.python.filters.Tests -from ImportMember im, ModuleObject m, AttrNode store_attr, string name +from ImportMember im, ModuleValue m, AttrNode store_attr, string name where - im.getModule().(ImportExpr).getImportedModuleName() = m.getName() and + m.importedAs(im.getModule().(ImportExpr).getImportedModuleName()) and im.getName() = name and /* Modification must be in a function, so it can occur during lifetime of the import value */ store_attr.getScope() instanceof Function and /* variable resulting from import must have a long lifetime */ not im.getScope() instanceof Function and store_attr.isStore() and - store_attr.getObject(name).refersTo(m) and + store_attr.getObject(name).pointsTo(m) and /* Import not in same module as modification. */ not im.getEnclosingModule() = store_attr.getScope().getEnclosingModule() and /* Modification is not in a test */ diff --git a/python/ql/src/Imports/ModuleImportsItself.ql b/python/ql/src/Imports/ModuleImportsItself.ql index d07d79ed9a3..d305d9a3596 100644 --- a/python/ql/src/Imports/ModuleImportsItself.ql +++ b/python/ql/src/Imports/ModuleImportsItself.ql @@ -12,11 +12,11 @@ import python -predicate modules_imports_itself(Import i, ModuleObject m) { - i.getEnclosingModule() = m.getModule() and +predicate modules_imports_itself(Import i, ModuleValue m) { + i.getEnclosingModule() = m.getScope() and m.importedAs(i.getAnImportedModuleName()) } -from Import i, ModuleObject m +from Import i, ModuleValue m where modules_imports_itself(i, m) select i, "The module '" + m.getName() + "' imports itself." diff --git a/python/ql/src/Imports/UnintentionalImport.ql b/python/ql/src/Imports/UnintentionalImport.ql index 3815b04f64a..8e396896e95 100644 --- a/python/ql/src/Imports/UnintentionalImport.ql +++ b/python/ql/src/Imports/UnintentionalImport.ql @@ -13,20 +13,20 @@ import python -predicate import_star(ImportStar imp, ModuleObject exporter) { +predicate import_star(ImportStar imp, ModuleValue exporter) { exporter.importedAs(imp.getImportedModuleName()) } -predicate all_defined(ModuleObject exporter) { - exporter.isC() +predicate all_defined(ModuleValue exporter) { + exporter.isBuiltin() or - exporter.getModule().(ImportTimeScope).definesName("__all__") + exporter.getScope().(ImportTimeScope).definesName("__all__") or - exporter.getModule().getInitModule().(ImportTimeScope).definesName("__all__") + exporter.getScope().getInitModule().(ImportTimeScope).definesName("__all__") } -from ImportStar imp, ModuleObject exporter +from ImportStar imp, ModuleValue exporter where import_star(imp, exporter) and not all_defined(exporter) select imp, "Import pollutes the enclosing namespace, as the imported module $@ does not define '__all__'.", exporter, exporter.getName() diff --git a/python/ql/src/Metrics/DirectImports.ql b/python/ql/src/Metrics/DirectImports.ql index 1eeb7694879..ec9114cddd9 100644 --- a/python/ql/src/Metrics/DirectImports.ql +++ b/python/ql/src/Metrics/DirectImports.ql @@ -11,6 +11,6 @@ */ import python -from ModuleObject m, int n -where n = count(ModuleObject imp | imp = m.getAnImportedModule()) -select m.getModule(), n \ No newline at end of file +from ModuleValue m, int n +where n = count(ModuleValue imp | imp = m.getAnImportedModule()) +select m.getScope(), n diff --git a/python/ql/src/Metrics/TransitiveImports.ql b/python/ql/src/Metrics/TransitiveImports.ql index 11fe7ee8f7e..cea731388f9 100644 --- a/python/ql/src/Metrics/TransitiveImports.ql +++ b/python/ql/src/Metrics/TransitiveImports.ql @@ -11,6 +11,6 @@ */ import python -from ModuleObject m, int n -where n = count(ModuleObject imp | imp = m.getAnImportedModule+() and imp != m) -select m.getModule(), n \ No newline at end of file +from ModuleValue m, int n +where n = count(ModuleValue imp | imp = m.getAnImportedModule+() and imp != m) +select m.getScope(), n diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 7884daa7b06..cf7153a6f06 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -167,6 +167,11 @@ class ModuleValue extends Value { this.(ModuleObjectInternal).hasCompleteExportInfo() } + /** Get a module that this module imports */ + ModuleValue getAnImportedModule() { + result.importedAs(this.getScope().getAnImportedModuleName()) + } + } module Module { From 408c49a61ca687e60ba168385970b43fbc9385da Mon Sep 17 00:00:00 2001 From: ggolawski <35563296+ggolawski@users.noreply.github.com> Date: Mon, 27 Jan 2020 22:31:51 +0100 Subject: [PATCH 052/148] Apply suggestions from code review Co-Authored-By: Felicity Chapman --- java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp b/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp index 9cc666a0366..9d0155e73e0 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp @@ -34,19 +34,19 @@ the query cannot be changed by a malicious user.

    -

    The third example uses Spring LdapQueryBuilder to build LDAP query. In addition to -simplifying building of complex search parameters, it also provides proper escaping of any -unsafe characters in search filters. DN is built using LdapNameBuilder, which also provides +

    The third example uses Spring LdapQueryBuilder to build an LDAP query. In addition to +simplifying the building of complex search parameters, it also provides proper escaping of any +unsafe characters in search filters. The DN is built using LdapNameBuilder, which also provides proper escaping.

    -

    The fourth example uses UnboundID Filter and DN classes to construct safe filter and +

    The fourth example uses UnboundID classes, Filter and DN, to construct a safe filter and base DN.

    -

    The fifth example shows how to build safe filter and DN using Apache LDAP API.

    +

    The fifth example shows how to build a safe filter and DN using the Apache LDAP API.

    From 7b2192d2e376ecd687bb851ffc85ff52ea86de99 Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Mon, 27 Jan 2020 22:34:15 +0100 Subject: [PATCH 053/148] Apply suggestion from code review --- java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp | 1 - 1 file changed, 1 deletion(-) diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp b/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp index 9d0155e73e0..120eac2b531 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp @@ -53,7 +53,6 @@ base DN.

  • OWASP: LDAP Injection Prevention Cheat Sheet.
  • -
  • OWASP: Preventing LDAP Injection in Java.
  • OWASP ESAPI: OWASP ESAPI.
  • Spring LdapQueryBuilder doc: LdapQueryBuilder.
  • Spring LdapNameBuilder doc: LdapNameBuilder.
  • From fd807d46d6c59338bbc2f7c09252bdbe3447e487 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Mon, 27 Jan 2020 16:38:07 -0800 Subject: [PATCH 054/148] C++: IR dataflow through modeled functions --- .../cpp/ir/dataflow/internal/DataFlowUtil.qll | 54 ++++++++++++++++++- .../dataflow/taint-tests/test_diff.expected | 4 -- .../dataflow/taint-tests/test_ir.expected | 4 ++ 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll index 24598bdaf12..23a94837620 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll @@ -6,6 +6,7 @@ private import cpp private import semmle.code.cpp.ir.IR private import semmle.code.cpp.controlflow.IRGuards private import semmle.code.cpp.ir.ValueNumbering +private import semmle.code.cpp.models.interfaces.DataFlow /** * A newtype wrapper to prevent accidental casts between `Node` and @@ -282,7 +283,58 @@ private predicate simpleInstructionLocalFlowStep(Instruction iFrom, Instruction // for variables that have escaped: for soundness, the IR has to assume that // every write to an unknown address can affect every escaped variable, and // this assumption shows up as data flowing through partial chi operands. - iTo.getAnOperand().(ChiTotalOperand).getDef() = iFrom + iTo.getAnOperand().(ChiTotalOperand).getDef() = iFrom or + // Flow from argument to return value + iTo = any(CallInstruction call | + exists(int indexIn | + modelFlowToReturnValue(call.getStaticCallTarget(), indexIn) and + iFrom = getACallArgumentOrIndirection(call, indexIn) + ) + ) + or + // Flow from input argument to output argument + // TODO: This won't work in practice as long as all aliased memory is tracked + // together in a single virtual variable. + iTo = any(WriteSideEffectInstruction outNode | + exists(CallInstruction call, int indexIn, int indexOut | + modelFlowToParameter(call.getStaticCallTarget(), indexIn, indexOut) and + iFrom = getACallArgumentOrIndirection(call, indexIn) and + outNode.getIndex() = indexOut and + outNode.getPrimaryInstruction() = call + ) + ) +} + +/** + * Get an instruction that goes into argument `argumentIndex` of `call`. This + * can be either directly or through one pointer indirection. + */ +private Instruction getACallArgumentOrIndirection(CallInstruction call, int argumentIndex) { + result = call.getPositionalArgument(argumentIndex) + or + exists(ReadSideEffectInstruction readSE | + // TODO: why are read side effect operands imprecise? + result = readSE.getSideEffectOperand().getAnyDef() and + readSE.getPrimaryInstruction() = call and + readSE.getIndex() = argumentIndex + ) +} + +private predicate modelFlowToParameter(Function f, int parameterIn, int parameterOut) { + exists(FunctionInput modelIn, FunctionOutput modelOut | + f.(DataFlowFunction).hasDataFlow(modelIn, modelOut) and + (modelIn.isParameter(parameterIn) or modelIn.isParameterDeref(parameterIn)) and + modelOut.isParameterDeref(parameterOut) + ) +} + +private predicate modelFlowToReturnValue(Function f, int parameterIn) { + // Data flow from parameter to return value + exists(FunctionInput modelIn, FunctionOutput modelOut | + f.(DataFlowFunction).hasDataFlow(modelIn, modelOut) and + (modelIn.isParameter(parameterIn) or modelIn.isParameterDeref(parameterIn)) and + (modelOut.isReturnValue() or modelOut.isReturnValueDeref()) + ) } /** diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected index 9354f098533..bd7652ff5bb 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected @@ -24,10 +24,6 @@ | taint.cpp:261:7:261:7 | taint.cpp:258:7:258:12 | AST only | | taint.cpp:351:7:351:7 | taint.cpp:330:6:330:11 | AST only | | taint.cpp:352:7:352:7 | taint.cpp:330:6:330:11 | AST only | -| taint.cpp:372:7:372:7 | taint.cpp:365:24:365:29 | AST only | -| taint.cpp:374:7:374:7 | taint.cpp:365:24:365:29 | AST only | -| taint.cpp:382:7:382:7 | taint.cpp:377:23:377:28 | AST only | -| taint.cpp:391:7:391:7 | taint.cpp:385:27:385:32 | AST only | | taint.cpp:423:7:423:7 | taint.cpp:422:14:422:19 | AST only | | taint.cpp:424:9:424:17 | taint.cpp:422:14:422:19 | AST only | | taint.cpp:429:7:429:7 | taint.cpp:428:13:428:18 | IR only | diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected index 258a2cca7c5..9e920e23dfc 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected @@ -15,4 +15,8 @@ | taint.cpp:291:7:291:7 | y | taint.cpp:275:6:275:11 | call to source | | taint.cpp:337:7:337:7 | t | taint.cpp:330:6:330:11 | call to source | | taint.cpp:350:7:350:7 | t | taint.cpp:330:6:330:11 | call to source | +| taint.cpp:372:7:372:7 | a | taint.cpp:365:24:365:29 | source | +| taint.cpp:374:7:374:7 | c | taint.cpp:365:24:365:29 | source | +| taint.cpp:382:7:382:7 | a | taint.cpp:377:23:377:28 | source | +| taint.cpp:391:7:391:7 | a | taint.cpp:385:27:385:32 | source | | taint.cpp:429:7:429:7 | b | taint.cpp:428:13:428:18 | call to source | From 1b9e37534172fff65673778fb452b26c639809e3 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Mon, 27 Jan 2020 16:44:41 -0800 Subject: [PATCH 055/148] C++: Move getACallArgumentOrIndirection --- .../cpp/ir/dataflow/DefaultTaintTracking.qll | 19 ++----------------- .../cpp/ir/dataflow/internal/DataFlowUtil.qll | 17 +++++++++++------ 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll index 5005d694a15..af2d617fafe 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -163,7 +163,7 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) { i2 = any(CallInstruction call | exists(int indexIn | modelTaintToReturnValue(call.getStaticCallTarget(), indexIn) and - i1 = getACallArgumentOrIndirection(call, indexIn) + i1 = DataFlow::getACallArgumentOrIndirection(call, indexIn) ) ) or @@ -175,28 +175,13 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) { i2 = any(WriteSideEffectInstruction outNode | exists(CallInstruction call, int indexIn, int indexOut | modelTaintToParameter(call.getStaticCallTarget(), indexIn, indexOut) and - i1 = getACallArgumentOrIndirection(call, indexIn) and + i1 = DataFlow::getACallArgumentOrIndirection(call, indexIn) and outNode.getIndex() = indexOut and outNode.getPrimaryInstruction() = call ) ) } -/** - * Get an instruction that goes into argument `argumentIndex` of `call`. This - * can be either directly or through one pointer indirection. - */ -private Instruction getACallArgumentOrIndirection(CallInstruction call, int argumentIndex) { - result = call.getPositionalArgument(argumentIndex) - or - exists(ReadSideEffectInstruction readSE | - // TODO: why are read side effect operands imprecise? - result = readSE.getSideEffectOperand().getAnyDef() and - readSE.getPrimaryInstruction() = call and - readSE.getIndex() = argumentIndex - ) -} - private predicate modelTaintToParameter(Function f, int parameterIn, int parameterOut) { exists(FunctionInput modelIn, FunctionOutput modelOut | f.(TaintFunction).hasTaintFlow(modelIn, modelOut) and diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll index 23a94837620..3e1fb7dde2a 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll @@ -265,11 +265,15 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) { } private predicate simpleInstructionLocalFlowStep(Instruction iFrom, Instruction iTo) { - iTo.(CopyInstruction).getSourceValue() = iFrom or - iTo.(PhiInstruction).getAnOperand().getDef() = iFrom or + iTo.(CopyInstruction).getSourceValue() = iFrom + or + iTo.(PhiInstruction).getAnOperand().getDef() = iFrom + or // Treat all conversions as flow, even conversions between different numeric types. - iTo.(ConvertInstruction).getUnary() = iFrom or - iTo.(InheritanceConversionInstruction).getUnary() = iFrom or + iTo.(ConvertInstruction).getUnary() = iFrom + or + iTo.(InheritanceConversionInstruction).getUnary() = iFrom + or // A chi instruction represents a point where a new value (the _partial_ // operand) may overwrite an old value (the _total_ operand), but the alias // analysis couldn't determine that it surely will overwrite every bit of it or @@ -283,7 +287,8 @@ private predicate simpleInstructionLocalFlowStep(Instruction iFrom, Instruction // for variables that have escaped: for soundness, the IR has to assume that // every write to an unknown address can affect every escaped variable, and // this assumption shows up as data flowing through partial chi operands. - iTo.getAnOperand().(ChiTotalOperand).getDef() = iFrom or + iTo.getAnOperand().(ChiTotalOperand).getDef() = iFrom + or // Flow from argument to return value iTo = any(CallInstruction call | exists(int indexIn | @@ -309,7 +314,7 @@ private predicate simpleInstructionLocalFlowStep(Instruction iFrom, Instruction * Get an instruction that goes into argument `argumentIndex` of `call`. This * can be either directly or through one pointer indirection. */ -private Instruction getACallArgumentOrIndirection(CallInstruction call, int argumentIndex) { +Instruction getACallArgumentOrIndirection(CallInstruction call, int argumentIndex) { result = call.getPositionalArgument(argumentIndex) or exists(ReadSideEffectInstruction readSE | From a2e54b1477e8f2ebbb57be93f70a358f956635d0 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 28 Jan 2020 09:37:54 +0100 Subject: [PATCH 056/148] add support for `this` references in classes that extend EventEmitter --- .../javascript/frameworks/NodeJSLib.qll | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 1262f212747..b5beb054204 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -892,6 +892,11 @@ module NodeJSLib { * Get a Node that refers to a NodeJS EventEmitter instance. */ DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) } + + /** + * Gets the node that will used for comparison when determining whether one EventEmitter can send an event to another EventEmitter. + */ + DataFlow::SourceNode getBaseEmitter() {result = this} } /** @@ -906,9 +911,7 @@ module NodeJSLib { * An instance of an EventEmitter that is imported through the 'events' module. */ private class ImportedNodeJSEventEmitter extends NodeJSEventEmitter { - ImportedNodeJSEventEmitter() { - this = getAnEventEmitterImport().getAnInstantiation() - } + ImportedNodeJSEventEmitter() { this = getAnEventEmitterImport().getAnInstantiation() } } /** @@ -922,20 +925,27 @@ module NodeJSLib { } /** - * An instantiation of a class that extends EventEmitter. - * + * An instantiation of a class that extends EventEmitter. + * * By extending `NodeJSEventEmitter' we get data-flow on the events passing through this EventEmitter. */ - private class CustomEventEmitter extends NodeJSEventEmitter { + class CustomEventEmitter extends NodeJSEventEmitter { + EventEmitterSubClass clazz; CustomEventEmitter() { - this = any(EventEmitterSubClass clazz).getAClassReference().getAnInstantiation() + this = clazz.getAnInstanceReference() + } + + // we define that events can flow between all instances of the same class, as we thereby support the `this` references in the class itself. + override DataFlow::SourceNode getBaseEmitter() { + result = clazz } } /** * A registration of an event handler on a NodeJS EventEmitter instance. */ - private class NodeJSEventRegistration extends EventRegistration::DefaultEventRegistration, DataFlow::MethodCallNode { + private class NodeJSEventRegistration extends EventRegistration::DefaultEventRegistration, + DataFlow::MethodCallNode { override NodeJSEventEmitter emitter; NodeJSEventRegistration() { this = emitter.ref().getAMethodCall(EventEmitter::on()) } @@ -944,9 +954,12 @@ module NodeJSLib { /** * A dispatch of an event on a NodeJS EventEmitter instance. */ - private class NodeJSEventDispatch extends EventDispatch::DefaultEventDispatch, DataFlow::MethodCallNode { + private class NodeJSEventDispatch extends EventDispatch::DefaultEventDispatch, + DataFlow::MethodCallNode { override NodeJSEventEmitter emitter; NodeJSEventDispatch() { this = emitter.ref().getAMethodCall("emit") } + + override EventRegistration::Range getAReceiver() { emitter.getBaseEmitter() = result.getEmitter().(NodeJSEventEmitter).getBaseEmitter() } } } From 082967a6292d853c53aeb5bfe2ebe63e4f119924 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 28 Jan 2020 09:38:38 +0100 Subject: [PATCH 057/148] add EventEmitter models for `net.createServer()` and `respjs`. --- .../javascript/frameworks/NodeJSLib.qll | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index b5beb054204..c6048b7a40f 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -962,4 +962,88 @@ module NodeJSLib { override EventRegistration::Range getAReceiver() { emitter.getBaseEmitter() = result.getEmitter().(NodeJSEventEmitter).getBaseEmitter() } } + + /** + * An instance of net.createServer(), which creates a new TCP/IPC server. + */ + private class NodeJSNetServer extends DataFlow::SourceNode { + NodeJSNetServer() { this = DataFlow::moduleMember("net", "createServer").getAnInvocation() } + + private DataFlow::SourceNode ref(DataFlow::TypeTracker t) { + t.start() and result = this + or + exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t)) + } + + /** + * Gets a reference to this server. + */ + DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) } + } + + /** + * A connection opened on a NodeJS net server. + */ + private class NodeJSNetServerConnection extends EventEmitter::Range { + NodeJSNetServer server; + + NodeJSNetServerConnection() { + exists(DataFlow::MethodCallNode call | + call = server.ref().getAMethodCall("on") and + call.getArgument(0).mayHaveStringValue("connection") + | + this = call.getCallback(1).getParameter(0) + ) + } + + DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) } + } + + /** + * A registration of an event handler on a NodeJS net server instance. + */ + private class NodeJSNetServerRegistration extends EventRegistration::DefaultEventRegistration, + DataFlow::MethodCallNode { + override NodeJSNetServerConnection emitter; + + NodeJSNetServerRegistration() { this = emitter.ref().getAMethodCall(EventEmitter::on()) } + } + + /** + * A data flow node representing data received from a client to a NodeJS net server, viewed as remote user input. + */ + private class NodeJSNetServerItemAsRemoteFlow extends RemoteFlowSource { + NodeJSNetServerRegistration reg; + + NodeJSNetServerItemAsRemoteFlow() { this = reg.getReceivedItem(_) } + + override string getSourceType() { result = "NodeJS server" } + } + + /** + * An instantiation of the `respjs` library, which is an EventEmitter. + */ + private class RespJS extends NodeJSEventEmitter { + RespJS() { + this = DataFlow::moduleImport("respjs").getAnInstantiation() + } + } + + /** + * A event dispatch that serializes the input data and emits the result on the "data" channel. + */ + private class RespWrite extends EventDispatch::DefaultEventDispatch, + DataFlow::MethodCallNode { + override RespJS emitter; + + RespWrite() { this = emitter.ref().getAMethodCall("write") } + + override string getChannel() { + result = "data" + } + + override DataFlow::Node getSentItem(int i) { + i = 0 and result = this.getArgument(i) + } + } } From b306571d52afbdce9d89471b7561d2f99e6aec55 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 28 Jan 2020 10:22:04 +0000 Subject: [PATCH 058/148] JS: Type-track react component factories --- .../semmle/javascript/frameworks/React.qll | 41 +++++++++++++++++-- .../TaintTracking/BasicTaintTracking.expected | 1 + .../TaintTracking/exportedReactComponent.jsx | 4 ++ .../TaintTracking/importedReactComponent.jsx | 5 +++ .../frameworks/ReactJS/exportedComponent.jsx | 3 ++ .../frameworks/ReactJS/importedComponent.jsx | 5 +++ .../frameworks/ReactJS/tests.expected | 6 +++ 7 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 javascript/ql/test/library-tests/TaintTracking/exportedReactComponent.jsx create mode 100644 javascript/ql/test/library-tests/TaintTracking/importedReactComponent.jsx create mode 100644 javascript/ql/test/library-tests/frameworks/ReactJS/exportedComponent.jsx create mode 100644 javascript/ql/test/library-tests/frameworks/ReactJS/importedComponent.jsx diff --git a/javascript/ql/src/semmle/javascript/frameworks/React.qll b/javascript/ql/src/semmle/javascript/frameworks/React.qll index e94f3c6bacd..a8c1793a5cc 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/React.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/React.qll @@ -38,10 +38,15 @@ abstract class ReactComponent extends ASTNode { abstract AbstractValue getAbstractComponent(); /** - * Gets the value that instantiates this component when invoked. + * Gets the function that instantiates this component when invoked. */ abstract DataFlow::SourceNode getComponentCreatorSource(); + /** + * Gets a reference to the function that instantiates this component when invoked. + */ + abstract DataFlow::SourceNode getAComponentCreatorReference(); + /** * Gets a reference to this component. */ @@ -181,7 +186,7 @@ abstract class ReactComponent extends ASTNode { * constructor of this component. */ DataFlow::SourceNode getACandidatePropsSource() { - result.flowsTo(getComponentCreatorSource().getAnInvocation().getArgument(0)) + result.flowsTo(getAComponentCreatorReference().getAnInvocation().getArgument(0)) or result = getADefaultPropsSource() or @@ -269,6 +274,19 @@ class FunctionalComponent extends ReactComponent, Function { override AbstractValue getAbstractComponent() { result = AbstractInstance::of(this) } + private DataFlow::SourceNode getAComponentCreatorReference(DataFlow::TypeTracker t) { + t.start() and + result = DataFlow::valueNode(this) + or + exists(DataFlow::TypeTracker t2 | + result = getAComponentCreatorReference(t2).track(t2, t) + ) + } + + override DataFlow::SourceNode getAComponentCreatorReference() { + result = getAComponentCreatorReference(DataFlow::TypeTracker::end()) + } + override DataFlow::SourceNode getComponentCreatorSource() { result = DataFlow::valueNode(this) } override DataFlow::SourceNode getADefaultPropsSource() { @@ -302,6 +320,10 @@ abstract private class SharedReactPreactClassComponent extends ReactComponent, C override AbstractValue getAbstractComponent() { result = AbstractInstance::of(this) } + override DataFlow::SourceNode getAComponentCreatorReference() { + result = DataFlow::valueNode(this).(DataFlow::ClassNode).getAClassReference() + } + override DataFlow::SourceNode getComponentCreatorSource() { result = DataFlow::valueNode(this) } override DataFlow::SourceNode getACandidateStateSource() { @@ -420,6 +442,19 @@ class ES5Component extends ReactComponent, ObjectExpr { override AbstractValue getAbstractComponent() { result = TAbstractObjectLiteral(this) } + private DataFlow::SourceNode getAComponentCreatorReference(DataFlow::TypeTracker t) { + t.start() and + result = create + or + exists(DataFlow::TypeTracker t2 | + result = getAComponentCreatorReference(t2).track(t2, t) + ) + } + + override DataFlow::SourceNode getAComponentCreatorReference() { + result = getAComponentCreatorReference(DataFlow::TypeTracker::end()) + } + override DataFlow::SourceNode getComponentCreatorSource() { result = create } override DataFlow::SourceNode getACandidateStateSource() { @@ -517,7 +552,7 @@ private class AnalyzedThisInBoundCallback extends AnalyzedNode, DataFlow::ThisNo private class ReactJSXElement extends JSXElement { ReactComponent component; - ReactJSXElement() { component.getComponentCreatorSource().flowsToExpr(getNameExpr()) } + ReactJSXElement() { component.getAComponentCreatorReference().flowsToExpr(getNameExpr()) } /** * Gets the component this element instantiates. diff --git a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected index da4105238bc..22c9a6c4576 100644 --- a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected @@ -59,6 +59,7 @@ typeInferenceMismatch | exceptions.js:144:9:144:16 | source() | exceptions.js:132:8:132:27 | returnThrownSource() | | exceptions.js:150:13:150:20 | source() | exceptions.js:153:10:153:10 | e | | exceptions.js:158:13:158:20 | source() | exceptions.js:161:10:161:10 | e | +| importedReactComponent.jsx:4:40:4:47 | source() | exportedReactComponent.jsx:2:10:2:19 | props.text | | indexOf.js:4:11:4:18 | source() | indexOf.js:9:10:9:10 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:17:14:17:14 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:20:14:20:14 | y | diff --git a/javascript/ql/test/library-tests/TaintTracking/exportedReactComponent.jsx b/javascript/ql/test/library-tests/TaintTracking/exportedReactComponent.jsx new file mode 100644 index 00000000000..6489e09a1e5 --- /dev/null +++ b/javascript/ql/test/library-tests/TaintTracking/exportedReactComponent.jsx @@ -0,0 +1,4 @@ +export function UnsafeReactComponent(props) { + sink(props.text); + return
    +} diff --git a/javascript/ql/test/library-tests/TaintTracking/importedReactComponent.jsx b/javascript/ql/test/library-tests/TaintTracking/importedReactComponent.jsx new file mode 100644 index 00000000000..5bef649e05d --- /dev/null +++ b/javascript/ql/test/library-tests/TaintTracking/importedReactComponent.jsx @@ -0,0 +1,5 @@ +import { UnsafeReactComponent } from "./exportedReactComponent"; + +export function render() { + return +} diff --git a/javascript/ql/test/library-tests/frameworks/ReactJS/exportedComponent.jsx b/javascript/ql/test/library-tests/frameworks/ReactJS/exportedComponent.jsx new file mode 100644 index 00000000000..4335b4bc308 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ReactJS/exportedComponent.jsx @@ -0,0 +1,3 @@ +export function MyComponent(props) { + return
    +} diff --git a/javascript/ql/test/library-tests/frameworks/ReactJS/importedComponent.jsx b/javascript/ql/test/library-tests/frameworks/ReactJS/importedComponent.jsx new file mode 100644 index 00000000000..edc333528a7 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/ReactJS/importedComponent.jsx @@ -0,0 +1,5 @@ +import { MyComponent } from "./exportedComponent"; + +export function render(color) { + return +} diff --git a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected index 9b737cf1ea3..93e4d0d258a 100644 --- a/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/ReactJS/tests.expected @@ -15,6 +15,7 @@ test_ReactComponent_getInstanceMethod | es5.js:1:31:11:1 | {\\n dis ... ;\\n }\\n} | render | es5.js:3:11:5:3 | functio ... v>;\\n } | | es5.js:18:33:22:1 | {\\n ren ... ;\\n }\\n} | render | es5.js:19:11:21:3 | functio ... 1>;\\n } | | es6.js:1:1:8:1 | class H ... ;\\n }\\n} | render | es6.js:2:9:4:3 | () {\\n ... v>;\\n } | +| exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | render | exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | | plainfn.js:1:1:3:1 | functio ... div>;\\n} | render | plainfn.js:1:1:3:1 | functio ... div>;\\n} | | plainfn.js:5:1:7:1 | functio ... iv");\\n} | render | plainfn.js:5:1:7:1 | functio ... iv");\\n} | | plainfn.js:9:1:12:1 | functio ... rn x;\\n} | render | plainfn.js:9:1:12:1 | functio ... rn x;\\n} | @@ -93,6 +94,7 @@ test_ReactComponent_ref | es6.js:14:1:20:1 | class H ... }\\n} | es6.js:16:9:16:12 | this | | es6.js:14:1:20:1 | class H ... }\\n} | es6.js:17:9:17:12 | this | | es6.js:14:1:20:1 | class H ... }\\n} | es6.js:18:9:18:12 | this | +| exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | exportedComponent.jsx:1:8:1:7 | this | | namedImport.js:3:1:3:28 | class C ... nent {} | namedImport.js:3:27:3:26 | this | | namedImport.js:5:1:5:20 | class D extends C {} | namedImport.js:5:19:5:18 | this | | plainfn.js:1:1:3:1 | functio ... div>;\\n} | plainfn.js:1:1:1:0 | this | @@ -189,6 +191,7 @@ test_ReactComponent_getADirectPropsSource | es5.js:18:33:22:1 | {\\n ren ... ;\\n }\\n} | es5.js:20:24:20:33 | this.props | | es6.js:1:1:8:1 | class H ... ;\\n }\\n} | es6.js:1:37:1:36 | args | | es6.js:1:1:8:1 | class H ... ;\\n }\\n} | es6.js:3:24:3:33 | this.props | +| exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | exportedComponent.jsx:1:29:1:33 | props | | namedImport.js:3:1:3:28 | class C ... nent {} | namedImport.js:3:27:3:26 | args | | namedImport.js:5:1:5:20 | class D extends C {} | namedImport.js:5:19:5:18 | args | | plainfn.js:1:1:3:1 | functio ... div>;\\n} | plainfn.js:1:16:1:20 | props | @@ -208,6 +211,7 @@ test_ReactComponent_getADirectPropsSource | thisAccesses.js:47:1:52:1 | class C ... }\\n} | thisAccesses.js:48:18:48:18 | y | test_ReactComponent_getACandidatePropsValue | es5.js:8:13:8:19 | 'world' | +| importedComponent.jsx:4:32:4:36 | color | | props.js:5:46:5:67 | "propFr ... tProps" | | props.js:7:22:7:34 | "propFromJSX" | | props.js:9:33:9:53 | "propFr ... ructor" | @@ -222,6 +226,7 @@ test_ReactComponent | es5.js:18:33:22:1 | {\\n ren ... ;\\n }\\n} | | es6.js:1:1:8:1 | class H ... ;\\n }\\n} | | es6.js:14:1:20:1 | class H ... }\\n} | +| exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | | namedImport.js:3:1:3:28 | class C ... nent {} | | namedImport.js:5:1:5:20 | class D extends C {} | | plainfn.js:1:1:3:1 | functio ... div>;\\n} | @@ -248,6 +253,7 @@ test_ReactComponent_getAPropRead | es5.js:1:31:11:1 | {\\n dis ... ;\\n }\\n} | name | es5.js:4:24:4:38 | this.props.name | | es5.js:18:33:22:1 | {\\n ren ... ;\\n }\\n} | name | es5.js:20:24:20:38 | this.props.name | | es6.js:1:1:8:1 | class H ... ;\\n }\\n} | name | es6.js:3:24:3:38 | this.props.name | +| exportedComponent.jsx:1:8:3:1 | functio ... r}}/>\\n} | color | exportedComponent.jsx:2:32:2:42 | props.color | | plainfn.js:1:1:3:1 | functio ... div>;\\n} | name | plainfn.js:2:22:2:31 | props.name | | preact.js:1:1:7:1 | class H ... }\\n} | name | preact.js:3:9:3:18 | props.name | | probably-a-component.js:1:1:6:1 | class H ... }\\n} | name | probably-a-component.js:3:9:3:23 | this.props.name | From f23438ea65662867eb499755beaa08003df215e2 Mon Sep 17 00:00:00 2001 From: Calum Grant Date: Tue, 28 Jan 2020 11:48:59 +0000 Subject: [PATCH 059/148] C#: Add test showing false positive --- .../DangerousNonShortCircuitLogic.cs | 3 +++ .../DangerousNonShortCircuitLogic.expected | 1 + 2 files changed, 4 insertions(+) diff --git a/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs b/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs index d52d45397f6..69e92fded38 100644 --- a/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs +++ b/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs @@ -20,6 +20,8 @@ class Test var b = true; b &= c.Method(); // GOOD b |= c[0]; // GOOD + + if (c == null | c.Method(out _)) ; // GOOD (false positive) } class C @@ -28,6 +30,7 @@ class Test public string Property { get; set; } public bool this[int i] { get { return false; } set { } } public bool Method() { return false; } + public bool Method(out int x) { x = 0; return false; } } } diff --git a/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.expected b/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.expected index 8973461ca05..6f1fe759e1e 100644 --- a/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.expected +++ b/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.expected @@ -2,3 +2,4 @@ | DangerousNonShortCircuitLogic.cs:16:13:16:40 | ... \| ... | Potentially dangerous use of non-short circuit logic. | | DangerousNonShortCircuitLogic.cs:17:13:17:28 | ... \| ... | Potentially dangerous use of non-short circuit logic. | | DangerousNonShortCircuitLogic.cs:18:13:18:34 | ... \| ... | Potentially dangerous use of non-short circuit logic. | +| DangerousNonShortCircuitLogic.cs:24:13:24:39 | ... \| ... | Potentially dangerous use of non-short circuit logic. | From cb16116b4d8cc095c2ed8d442f3f03fa88848755 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 28 Jan 2020 14:00:26 +0100 Subject: [PATCH 060/148] adjust type-tracking on custom EventEmitters --- .../javascript/frameworks/NodeJSLib.qll | 21 +++++++++--------- .../frameworks/EventEmitter/customEmitter.js | 22 +++++++++++++++++++ .../frameworks/EventEmitter/test.expected | 6 +++++ 3 files changed, 38 insertions(+), 11 deletions(-) create mode 100644 javascript/ql/test/library-tests/frameworks/EventEmitter/customEmitter.js diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index c6048b7a40f..ddc6756cc82 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -892,11 +892,6 @@ module NodeJSLib { * Get a Node that refers to a NodeJS EventEmitter instance. */ DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) } - - /** - * Gets the node that will used for comparison when determining whether one EventEmitter can send an event to another EventEmitter. - */ - DataFlow::SourceNode getBaseEmitter() {result = this} } /** @@ -932,12 +927,18 @@ module NodeJSLib { class CustomEventEmitter extends NodeJSEventEmitter { EventEmitterSubClass clazz; CustomEventEmitter() { - this = clazz.getAnInstanceReference() + if exists(clazz.getAClassReference().getAnInstantiation()) then + this = clazz.getAClassReference().getAnInstantiation() + else + // In case there are no explicit instantiations of the clazz, then we still want to track data flow between `this` nodes. + // This cannot produce false flow as the `.ref()` method below is always used when creating event-registrations/event-dispatches. + this = clazz } - // we define that events can flow between all instances of the same class, as we thereby support the `this` references in the class itself. - override DataFlow::SourceNode getBaseEmitter() { - result = clazz + override DataFlow::SourceNode ref() { + result = NodeJSEventEmitter.super.ref() and not this = clazz + or + result = clazz.getAReceiverNode() } } @@ -959,8 +960,6 @@ module NodeJSLib { override NodeJSEventEmitter emitter; NodeJSEventDispatch() { this = emitter.ref().getAMethodCall("emit") } - - override EventRegistration::Range getAReceiver() { emitter.getBaseEmitter() = result.getEmitter().(NodeJSEventEmitter).getBaseEmitter() } } /** diff --git a/javascript/ql/test/library-tests/frameworks/EventEmitter/customEmitter.js b/javascript/ql/test/library-tests/frameworks/EventEmitter/customEmitter.js new file mode 100644 index 00000000000..fe3dafd31a1 --- /dev/null +++ b/javascript/ql/test/library-tests/frameworks/EventEmitter/customEmitter.js @@ -0,0 +1,22 @@ +const EventEmitter = require("events"); + +class MyEmitter extends EventEmitter { + foo() { + this.emit("foo", "bar"); + this.on("foo", (data) => {}); + } +} + +class MySecondEmitter extends EventEmitter { + foo() { + this.emit("bar", "baz"); + this.on("bar", (data) => {}); + } +} + +var x = new MySecondEmitter(); +x.emit("bar", "baz2"); + +var y = new MySecondEmitter(); +y.emit("bar", "baz3"); +y.on("bar", (yData) => {}) \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected index 33fa8385978..c8526d5eecf 100644 --- a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected +++ b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected @@ -1,3 +1,9 @@ +| customEmitter.js:5:20:5:24 | "bar" | customEmitter.js:6:19:6:22 | data | +| customEmitter.js:12:21:12:25 | "baz" | customEmitter.js:13:23:13:26 | data | +| customEmitter.js:12:21:12:25 | "baz" | customEmitter.js:22:14:22:18 | yData | +| customEmitter.js:18:15:18:20 | "baz2" | customEmitter.js:13:23:13:26 | data | +| customEmitter.js:21:15:21:20 | "baz3" | customEmitter.js:13:23:13:26 | data | +| customEmitter.js:21:15:21:20 | "baz3" | customEmitter.js:22:14:22:18 | yData | | tst.js:9:23:9:33 | 'FirstData' | tst.js:6:40:6:44 | first | | tst.js:10:24:10:35 | 'SecondData' | tst.js:7:32:7:37 | second | | tst.js:15:24:15:39 | 'OtherFirstData' | tst.js:14:41:14:50 | otherFirst | From 0b3821c8286d37fbd29571a192bd8a91ec123c33 Mon Sep 17 00:00:00 2001 From: Calum Grant Date: Tue, 28 Jan 2020 13:59:33 +0000 Subject: [PATCH 061/148] C#: Remove false positive for out params --- csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql | 3 ++- .../DangerousNonShortCircuitLogic.cs | 3 ++- .../DangerousNonShortCircuitLogic.expected | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql b/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql index f8fcc1d1e12..a789aeab8d7 100644 --- a/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql +++ b/csharp/ql/src/Likely Bugs/DangerousNonShortCircuitLogic.ql @@ -27,7 +27,8 @@ class DangerousExpression extends Expr { e instanceof MethodCall or e instanceof ArrayAccess - ) + ) and + not exists(Expr e | this = e.getParent*() | e.(Call).getTarget().getAParameter().isOutOrRef()) } } diff --git a/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs b/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs index 69e92fded38..0d10e11b7f6 100644 --- a/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs +++ b/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.cs @@ -21,7 +21,8 @@ class Test b &= c.Method(); // GOOD b |= c[0]; // GOOD - if (c == null | c.Method(out _)) ; // GOOD (false positive) + if (c == null | c.Method(out _)) ; // GOOD + if (c == null | (c.Method() | c.Method(out _))) ; // GOOD } class C diff --git a/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.expected b/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.expected index 6f1fe759e1e..8973461ca05 100644 --- a/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.expected +++ b/csharp/ql/test/query-tests/Likely Bugs/DangerousNonShortCircuitLogic/DangerousNonShortCircuitLogic.expected @@ -2,4 +2,3 @@ | DangerousNonShortCircuitLogic.cs:16:13:16:40 | ... \| ... | Potentially dangerous use of non-short circuit logic. | | DangerousNonShortCircuitLogic.cs:17:13:17:28 | ... \| ... | Potentially dangerous use of non-short circuit logic. | | DangerousNonShortCircuitLogic.cs:18:13:18:34 | ... \| ... | Potentially dangerous use of non-short circuit logic. | -| DangerousNonShortCircuitLogic.cs:24:13:24:39 | ... \| ... | Potentially dangerous use of non-short circuit logic. | From aea365c4244c2fd21e856746a02091e451235a1d Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 28 Jan 2020 15:09:31 +0100 Subject: [PATCH 062/148] adjust API naming --- .../ql/src/semmle/javascript/Promises.qll | 26 +++++++------- .../javascript/dataflow/Configuration.qll | 34 +++++++++++++------ 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index e59f77924cb..cac97e54e09 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -150,7 +150,7 @@ private module PromiseFlow { this = promise } - override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and pred = promise.getResolveParameter().getACall().getArgument(0) and succ = this @@ -163,7 +163,7 @@ private module PromiseFlow { succ = this } - override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { // Copy the value of a resolved promise to the value of this promise. prop = resolveField() and pred = promise.getResolveParameter().getACall().getArgument(0) and @@ -180,13 +180,13 @@ private module PromiseFlow { this = promise } - override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and pred = promise.getValue() and succ = this } - override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { // Copy the value of a resolved promise to the value of this promise. prop = resolveField() and pred = promise.getValue() and @@ -207,7 +207,7 @@ private module PromiseFlow { operand.getEnclosingExpr() = await.getOperand() } - override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and succ = this and pred = operand @@ -226,7 +226,7 @@ private module PromiseFlow { this.getMethodName() = "then" } - override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and pred = getReceiver() and succ = getCallback(0).getParameter(0) @@ -236,7 +236,7 @@ private module PromiseFlow { succ = getCallback(1).getParameter(0) } - override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { not exists(this.getArgument(1)) and prop = rejectField() and pred = getReceiver() and @@ -248,7 +248,7 @@ private module PromiseFlow { succ = this } - override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and pred = getCallback([0..1]).getAReturn() and succ = this @@ -267,13 +267,13 @@ private module PromiseFlow { this.getMethodName() = "catch" } - override predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and pred = getReceiver() and succ = getCallback(0).getParameter(0) } - override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and pred = getReceiver().getALocalSource() and succ = this @@ -284,7 +284,7 @@ private module PromiseFlow { succ = this } - override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and pred = getCallback(0).getExceptionalReturn() and succ = this @@ -303,7 +303,7 @@ private module PromiseFlow { this.getMethodName() = "finally" } - override predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { (prop = resolveField() or prop = rejectField()) and pred = getReceiver() and succ = this @@ -314,7 +314,7 @@ private module PromiseFlow { succ = this } - override predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and pred = getCallback(0).getExceptionalReturn() and succ = this diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index 5c484651b40..f281a8aa23e 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -225,19 +225,25 @@ abstract class Configuration extends string { } /** + * EXPERIMENTAL. This API may change in the future. + * * Holds if `pred` should be stored in the object `succ` under the property `prop`. */ predicate isAdditionalStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } /** + * EXPERIMENTAL. This API may change in the future. + * * Holds if the property `prop` of the object `pred` should be loaded into `succ`. */ predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } /** + * EXPERIMENTAL. This API may change in the future. + * * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. */ - predicate isAdditionalCopyPropertyStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { + predicate isAdditionalLoadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } } @@ -487,22 +493,28 @@ abstract class AdditionalFlowStep extends DataFlow::Node { } /** + * EXPERIMENTAL. This API may change in the future. + * * Holds if `pred` should be stored in the object `succ` under the property `prop`. */ cached - predicate store(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } /** + * EXPERIMENTAL. This API may change in the future. + * * Holds if the property `prop` of the object `pred` should be loaded into `succ`. */ cached - predicate load(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } /** + * EXPERIMENTAL. This API may change in the future. + * * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. */ cached - predicate copyProperty(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } + predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() } } /** @@ -607,7 +619,7 @@ private predicate exploratoryFlowStep( basicLoadStep(pred, succ, _) or isAdditionalStoreStep(pred, succ, _, cfg) or isAdditionalLoadStep(pred, succ, _, cfg) or - isAdditionalCopyPropertyStep(pred, succ, _, cfg) or + isAdditionalLoadStoreStep(pred, succ, _, cfg) or // the following two disjuncts taken together over-approximate flow through // higher-order calls callback(pred, succ) or @@ -830,7 +842,7 @@ private predicate reachesReturn( private predicate isAdditionalLoadStep( DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg ) { - any(AdditionalFlowStep s).load(pred, succ, prop) + any(AdditionalFlowStep s).loadStep(pred, succ, prop) or cfg.isAdditionalLoadStep(pred, succ, prop) } @@ -841,7 +853,7 @@ private predicate isAdditionalLoadStep( private predicate isAdditionalStoreStep( DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg ) { - any(AdditionalFlowStep s).store(pred, succ, prop) + any(AdditionalFlowStep s).storeStep(pred, succ, prop) or cfg.isAdditionalStoreStep(pred, succ, prop) } @@ -849,12 +861,12 @@ private predicate isAdditionalStoreStep( /** * Holds if the property `prop` should be copied from the object `pred` to the object `succ`. */ -private predicate isAdditionalCopyPropertyStep( +private predicate isAdditionalLoadStoreStep( DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg ) { - any(AdditionalFlowStep s).copyProperty(pred, succ, prop) + any(AdditionalFlowStep s).loadStoreStep(pred, succ, prop) or - cfg.isAdditionalCopyPropertyStep(pred, succ, prop) + cfg.isAdditionalLoadStoreStep(pred, succ, prop) } /** @@ -895,7 +907,7 @@ private predicate reachableFromStoreBase( ( flowStep(mid, cfg, nd, newSummary) or - isAdditionalCopyPropertyStep(mid, nd, prop, cfg) and + isAdditionalLoadStoreStep(mid, nd, prop, cfg) and newSummary = PathSummary::level() ) and summary = oldSummary.appendValuePreserving(newSummary) From 6b377d7ad441e1886fed95180ec8450c0061ce53 Mon Sep 17 00:00:00 2001 From: Calum Grant Date: Tue, 28 Jan 2020 14:59:25 +0000 Subject: [PATCH 063/148] C#: Analysis change notes --- change-notes/1.24/analysis-csharp.md | 1 + 1 file changed, 1 insertion(+) diff --git a/change-notes/1.24/analysis-csharp.md b/change-notes/1.24/analysis-csharp.md index 2df9934dc48..7c7e879ab73 100644 --- a/change-notes/1.24/analysis-csharp.md +++ b/change-notes/1.24/analysis-csharp.md @@ -14,6 +14,7 @@ The following changes in version 1.24 affect C# analysis in all applications. | **Query** | **Expected impact** | **Change** | |------------------------------|------------------------|-----------------------------------| | Useless assignment to local variable (`cs/useless-assignment-to-local`) | Fewer false positive results | Results have been removed when the variable is named `_` in a `foreach` statement. | +| Potentially dangerous use of non-short-circuit logic (`cs/non-short-circuit`) | Fewer false positive results | Results have been removed when the expression contains an `out` parameter. | | Dereferenced variable may be null (`cs/dereferenced-value-may-be-null`) | More results | Results are reported from parameters with a default value of `null`. | ## Removal of old queries From 1b1fded535b109982684767286d00316cb64593c Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 28 Jan 2020 10:41:53 -0700 Subject: [PATCH 064/148] C++/C#: Add new `MemoryAccessKind` to represent entire allocation --- .../ir/implementation/MemoryAccessKind.qll | 11 +++++++ .../code/cpp/ir/implementation/Opcode.qll | 29 +++++++++++++++++-- .../ir/implementation/MemoryAccessKind.qll | 11 +++++++ .../code/csharp/ir/implementation/Opcode.qll | 29 +++++++++++++++++-- 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/MemoryAccessKind.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/MemoryAccessKind.qll index f5c6de314ab..eac4d333afc 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/MemoryAccessKind.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/MemoryAccessKind.qll @@ -1,6 +1,7 @@ private newtype TMemoryAccessKind = TIndirectMemoryAccess() or TBufferMemoryAccess() or + TEntireAllocationMemoryAccess() or TEscapedMemoryAccess() or TNonLocalMemoryAccess() or TPhiMemoryAccess() or @@ -43,6 +44,16 @@ class BufferMemoryAccess extends MemoryAccessKind, TBufferMemoryAccess { final override predicate usesAddressOperand() { any() } } +/** + * The operand or results accesses all memory in the contiguous allocation that contains the address + * specified by the `AddressOperand` on the same instruction. + */ +class EntireAllocationMemoryAccess extends MemoryAccessKind, TEntireAllocationMemoryAccess { + override string toString() { result = "alloc" } + + final override predicate usesAddressOperand() { any() } +} + /** * The operand or result accesses all memory whose address has escaped. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll index d39d8c30a1a..4b1124cf27e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll @@ -232,6 +232,31 @@ abstract class BufferReadOpcode extends BufferAccessOpcode { final override MemoryAccessKind getReadMemoryAccess() { result instanceof BufferMemoryAccess } } +/** + * An opcode that access an entire memory allocation. + */ +abstract class EntireAllocationAccessOpcode extends Opcode { + final override predicate hasAddressOperand() { any() } +} + +/** + * An opcode that write to an entire memory allocation. + */ +abstract class EntireAllocationWriteOpcode extends EntireAllocationAccessOpcode { + final override MemoryAccessKind getWriteMemoryAccess() { + result instanceof EntireAllocationMemoryAccess + } +} + +/** + * An opcode that reads from an entire memory allocation. + */ +abstract class EntireAllocationReadOpcode extends EntireAllocationAccessOpcode { + final override MemoryAccessKind getReadMemoryAccess() { + result instanceof EntireAllocationMemoryAccess + } +} + /** * An opcode that accesses a memory buffer whose size is determined by a `BufferSizeOperand`. */ @@ -325,7 +350,7 @@ module Opcode { final override string toString() { result = "InitializeParameter" } } - class InitializeIndirection extends IndirectWriteOpcode, TInitializeIndirection { + class InitializeIndirection extends EntireAllocationWriteOpcode, TInitializeIndirection { final override string toString() { result = "InitializeIndirection" } } @@ -349,7 +374,7 @@ module Opcode { final override string toString() { result = "ReturnVoid" } } - class ReturnIndirection extends IndirectReadOpcode, TReturnIndirection { + class ReturnIndirection extends EntireAllocationReadOpcode, TReturnIndirection { final override string toString() { result = "ReturnIndirection" } final override predicate hasOperandInternal(OperandTag tag) { diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/MemoryAccessKind.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/MemoryAccessKind.qll index f5c6de314ab..eac4d333afc 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/MemoryAccessKind.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/MemoryAccessKind.qll @@ -1,6 +1,7 @@ private newtype TMemoryAccessKind = TIndirectMemoryAccess() or TBufferMemoryAccess() or + TEntireAllocationMemoryAccess() or TEscapedMemoryAccess() or TNonLocalMemoryAccess() or TPhiMemoryAccess() or @@ -43,6 +44,16 @@ class BufferMemoryAccess extends MemoryAccessKind, TBufferMemoryAccess { final override predicate usesAddressOperand() { any() } } +/** + * The operand or results accesses all memory in the contiguous allocation that contains the address + * specified by the `AddressOperand` on the same instruction. + */ +class EntireAllocationMemoryAccess extends MemoryAccessKind, TEntireAllocationMemoryAccess { + override string toString() { result = "alloc" } + + final override predicate usesAddressOperand() { any() } +} + /** * The operand or result accesses all memory whose address has escaped. */ diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll index d39d8c30a1a..4b1124cf27e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll @@ -232,6 +232,31 @@ abstract class BufferReadOpcode extends BufferAccessOpcode { final override MemoryAccessKind getReadMemoryAccess() { result instanceof BufferMemoryAccess } } +/** + * An opcode that access an entire memory allocation. + */ +abstract class EntireAllocationAccessOpcode extends Opcode { + final override predicate hasAddressOperand() { any() } +} + +/** + * An opcode that write to an entire memory allocation. + */ +abstract class EntireAllocationWriteOpcode extends EntireAllocationAccessOpcode { + final override MemoryAccessKind getWriteMemoryAccess() { + result instanceof EntireAllocationMemoryAccess + } +} + +/** + * An opcode that reads from an entire memory allocation. + */ +abstract class EntireAllocationReadOpcode extends EntireAllocationAccessOpcode { + final override MemoryAccessKind getReadMemoryAccess() { + result instanceof EntireAllocationMemoryAccess + } +} + /** * An opcode that accesses a memory buffer whose size is determined by a `BufferSizeOperand`. */ @@ -325,7 +350,7 @@ module Opcode { final override string toString() { result = "InitializeParameter" } } - class InitializeIndirection extends IndirectWriteOpcode, TInitializeIndirection { + class InitializeIndirection extends EntireAllocationWriteOpcode, TInitializeIndirection { final override string toString() { result = "InitializeIndirection" } } @@ -349,7 +374,7 @@ module Opcode { final override string toString() { result = "ReturnVoid" } } - class ReturnIndirection extends IndirectReadOpcode, TReturnIndirection { + class ReturnIndirection extends EntireAllocationReadOpcode, TReturnIndirection { final override string toString() { result = "ReturnIndirection" } final override predicate hasOperandInternal(OperandTag tag) { From b15dd827321cb345bafd00abf219495135ea54fe Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 28 Jan 2020 10:47:37 -0700 Subject: [PATCH 065/148] C++/C#: Share alias analysis between C++ and C# --- .../aliased_ssa/internal/AliasAnalysis.qll | 39 ++--- .../internal/AliasAnalysisImports.qll | 3 + .../internal/AliasAnalysisInternal.qll | 1 + .../unaliased_ssa/internal/AliasAnalysis.qll | 39 ++--- .../internal/AliasAnalysisImports.qll | 3 + .../internal/AliasAnalysisInternal.qll | 1 + .../code/cpp/ir/internal/IRCppLanguage.qll | 14 ++ .../unaliased_ssa/internal/AliasAnalysis.qll | 147 +++++++++--------- .../internal/AliasAnalysisImports.qll | 48 ++++++ .../internal/AliasAnalysisInternal.qll | 1 + .../csharp/ir/internal/IRCSharpLanguage.qll | 13 ++ 11 files changed, 178 insertions(+), 131 deletions(-) create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisImports.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll create mode 100644 csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll index 99f6b545806..3c6591404b2 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll @@ -1,21 +1,9 @@ private import AliasAnalysisInternal -private import cpp private import InputIR -private import semmle.code.cpp.ir.internal.IntegerConstant as Ints -private import semmle.code.cpp.ir.implementation.IRConfiguration -private import semmle.code.cpp.models.interfaces.Alias +private import AliasAnalysisImports private class IntValue = Ints::IntValue; -/** - * Gets the offset of field `field` in bits. - */ -private IntValue getFieldBitOffset(Field field) { - if field instanceof BitField - then result = Ints::add(Ints::mul(field.getByteOffset(), 8), field.(BitField).getBitOffset()) - else result = Ints::mul(field.getByteOffset(), 8) -} - /** * Holds if the operand `tag` of instruction `instr` is used in a way that does * not result in any address held in that operand from escaping beyond the @@ -36,7 +24,7 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) { instr instanceof PointerDiffInstruction or // Converting an address to a `bool` does not escape the address. - instr.(ConvertInstruction).getResultType() instanceof BoolType + instr.(ConvertInstruction).getResultIRType() instanceof IRBooleanType ) ) or @@ -107,13 +95,10 @@ private predicate operandIsPropagated(Operand operand, IntValue bitOffset) { bitOffset = Ints::unknown() or // Conversion to another pointer type propagates the source address. - exists(ConvertInstruction convert, Type resultType | + exists(ConvertInstruction convert, IRType resultType | convert = instr and - resultType = convert.getResultType() and - ( - resultType instanceof PointerType or - resultType instanceof Class //REVIEW: Remove when all glvalues are pointers - ) and + resultType = convert.getResultIRType() and + resultType instanceof IRAddressType and bitOffset = 0 ) or @@ -127,7 +112,7 @@ private predicate operandIsPropagated(Operand operand, IntValue bitOffset) { or // Computing a field address from a pointer propagates the address plus the // offset of the field. - bitOffset = getFieldBitOffset(instr.(FieldAddressInstruction).getField()) + bitOffset = Language::getFieldBitOffset(instr.(FieldAddressInstruction).getField()) or // A copy propagates the source value. operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0 @@ -208,7 +193,7 @@ private predicate operandReturned(Operand operand, IntValue bitOffset) { } private predicate isArgumentForParameter(CallInstruction ci, Operand operand, Instruction init) { - exists(Function f | + exists(Language::Function f | ci = operand.getUse() and f = ci.getStaticCallTarget() and ( @@ -219,27 +204,27 @@ private predicate isArgumentForParameter(CallInstruction ci, Operand operand, In init.getEnclosingFunction() = f and operand instanceof ThisArgumentOperand ) and - not f.isVirtual() and - not f instanceof AliasFunction + not Language::isFunctionVirtual(f) and + not f instanceof AliasModels::AliasFunction ) } private predicate isAlwaysReturnedArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterIsAlwaysReturned(operand.(PositionalArgumentOperand).getIndex()) ) } private predicate isOnlyEscapesViaReturnArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) ) } private predicate isNeverEscapesArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) ) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisImports.qll new file mode 100644 index 00000000000..c4aeaf93cce --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisImports.qll @@ -0,0 +1,3 @@ +import semmle.code.cpp.ir.implementation.IRConfiguration +import semmle.code.cpp.ir.internal.IntegerConstant as Ints +import semmle.code.cpp.models.interfaces.Alias as AliasModels diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll index 003b7008619..80dbb888db7 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll @@ -1 +1,2 @@ +import semmle.code.cpp.ir.internal.IRCppLanguage as Language import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as InputIR diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll index 99f6b545806..3c6591404b2 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll @@ -1,21 +1,9 @@ private import AliasAnalysisInternal -private import cpp private import InputIR -private import semmle.code.cpp.ir.internal.IntegerConstant as Ints -private import semmle.code.cpp.ir.implementation.IRConfiguration -private import semmle.code.cpp.models.interfaces.Alias +private import AliasAnalysisImports private class IntValue = Ints::IntValue; -/** - * Gets the offset of field `field` in bits. - */ -private IntValue getFieldBitOffset(Field field) { - if field instanceof BitField - then result = Ints::add(Ints::mul(field.getByteOffset(), 8), field.(BitField).getBitOffset()) - else result = Ints::mul(field.getByteOffset(), 8) -} - /** * Holds if the operand `tag` of instruction `instr` is used in a way that does * not result in any address held in that operand from escaping beyond the @@ -36,7 +24,7 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) { instr instanceof PointerDiffInstruction or // Converting an address to a `bool` does not escape the address. - instr.(ConvertInstruction).getResultType() instanceof BoolType + instr.(ConvertInstruction).getResultIRType() instanceof IRBooleanType ) ) or @@ -107,13 +95,10 @@ private predicate operandIsPropagated(Operand operand, IntValue bitOffset) { bitOffset = Ints::unknown() or // Conversion to another pointer type propagates the source address. - exists(ConvertInstruction convert, Type resultType | + exists(ConvertInstruction convert, IRType resultType | convert = instr and - resultType = convert.getResultType() and - ( - resultType instanceof PointerType or - resultType instanceof Class //REVIEW: Remove when all glvalues are pointers - ) and + resultType = convert.getResultIRType() and + resultType instanceof IRAddressType and bitOffset = 0 ) or @@ -127,7 +112,7 @@ private predicate operandIsPropagated(Operand operand, IntValue bitOffset) { or // Computing a field address from a pointer propagates the address plus the // offset of the field. - bitOffset = getFieldBitOffset(instr.(FieldAddressInstruction).getField()) + bitOffset = Language::getFieldBitOffset(instr.(FieldAddressInstruction).getField()) or // A copy propagates the source value. operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0 @@ -208,7 +193,7 @@ private predicate operandReturned(Operand operand, IntValue bitOffset) { } private predicate isArgumentForParameter(CallInstruction ci, Operand operand, Instruction init) { - exists(Function f | + exists(Language::Function f | ci = operand.getUse() and f = ci.getStaticCallTarget() and ( @@ -219,27 +204,27 @@ private predicate isArgumentForParameter(CallInstruction ci, Operand operand, In init.getEnclosingFunction() = f and operand instanceof ThisArgumentOperand ) and - not f.isVirtual() and - not f instanceof AliasFunction + not Language::isFunctionVirtual(f) and + not f instanceof AliasModels::AliasFunction ) } private predicate isAlwaysReturnedArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterIsAlwaysReturned(operand.(PositionalArgumentOperand).getIndex()) ) } private predicate isOnlyEscapesViaReturnArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) ) } private predicate isNeverEscapesArgument(Operand operand) { - exists(AliasFunction f | + exists(AliasModels::AliasFunction f | f = operand.getUse().(CallInstruction).getStaticCallTarget() and f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) ) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll new file mode 100644 index 00000000000..c4aeaf93cce --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll @@ -0,0 +1,3 @@ +import semmle.code.cpp.ir.implementation.IRConfiguration +import semmle.code.cpp.ir.internal.IntegerConstant as Ints +import semmle.code.cpp.models.interfaces.Alias as AliasModels diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll index 8a9e43e14a3..eeb64333bf2 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll @@ -1 +1,2 @@ +import semmle.code.cpp.ir.internal.IRCppLanguage as Language import semmle.code.cpp.ir.implementation.raw.IR as InputIR diff --git a/cpp/ql/src/semmle/code/cpp/ir/internal/IRCppLanguage.qll b/cpp/ql/src/semmle/code/cpp/ir/internal/IRCppLanguage.qll index 2adea27afb4..3bde6c7d501 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/internal/IRCppLanguage.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/internal/IRCppLanguage.qll @@ -80,3 +80,17 @@ predicate hasPotentialLoop(Function f) { } predicate hasGoto(Function f) { exists(Cpp::GotoStmt s | s.getEnclosingFunction() = f) } + +/** + * Gets the offset of field `field` in bits. + */ +int getFieldBitOffset(Field field) { + if field instanceof Cpp::BitField + then result = (field.getByteOffset() * 8) + field.(Cpp::BitField).getBitOffset() + else result = field.getByteOffset() * 8 +} + +/** + * Holds if the specified `Function` can be overridden in a derived class. + */ +predicate isFunctionVirtual(Function f) { f.isVirtual() } diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll index a90eec8c17c..3c6591404b2 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll @@ -1,40 +1,11 @@ private import AliasAnalysisInternal -private import csharp private import InputIR -private import semmle.code.csharp.ir.internal.IntegerConstant as Ints +private import AliasAnalysisImports private class IntValue = Ints::IntValue; /** - * Converts the bit count in `bits` to a byte count and a bit count in the form - * bytes:bits. - */ -bindingset[bits] -string bitsToBytesAndBits(int bits) { result = (bits / 8).toString() + ":" + (bits % 8).toString() } - -/** - * Gets a printable string for a bit offset with possibly unknown value. - */ -bindingset[bitOffset] -string getBitOffsetString(IntValue bitOffset) { - if Ints::hasValue(bitOffset) - then - if bitOffset >= 0 - then result = "+" + bitsToBytesAndBits(bitOffset) - else result = "-" + bitsToBytesAndBits(Ints::neg(bitOffset)) - else result = "+?" -} - -///** -// * Gets the offset of field `field` in bits. -// */ -//private IntValue getFieldBitOffset(Field field) { -// if field instanceof BitField -// then result = Ints::add(Ints::mul(field.getByteOffset(), 8), field.(BitField).getBitOffset()) -// else result = Ints::mul(field.getByteOffset(), 8) -//} -/** - * Holds if the operand `operand` of instruction `instr` is used in a way that does + * Holds if the operand `tag` of instruction `instr` is used in a way that does * not result in any address held in that operand from escaping beyond the * instruction. */ @@ -51,6 +22,9 @@ private predicate operandIsConsumedWithoutEscaping(Operand operand) { or // Neither operand of a PointerDiff escapes. instr instanceof PointerDiffInstruction + or + // Converting an address to a `bool` does not escape the address. + instr.(ConvertInstruction).getResultIRType() instanceof IRBooleanType ) ) or @@ -64,6 +38,7 @@ private predicate operandEscapesDomain(Operand operand) { not isArgumentForParameter(_, operand, _) and not isOnlyEscapesViaReturnArgument(operand) and not operand.getUse() instanceof ReturnValueInstruction and + not operand.getUse() instanceof ReturnIndirectionInstruction and not operand instanceof PhiInputOperand } @@ -94,7 +69,7 @@ IntValue getPointerBitOffset(PointerOffsetInstruction instr) { } /** - * Holds if any address held in operand `operand` of instruction `instr` is + * Holds if any address held in operand `tag` of instruction `instr` is * propagated to the result of `instr`, offset by the number of bits in * `bitOffset`. If the address is propagated, but the offset is not known to be * a constant, then `bitOffset` is unknown. @@ -103,48 +78,42 @@ private predicate operandIsPropagated(Operand operand, IntValue bitOffset) { exists(Instruction instr | instr = operand.getUse() and ( - // REVIEW: See the REVIEW comment bellow - // // Converting to a non-virtual base class adds the offset of the base class. - // exists(ConvertToNonVirtualBaseInstruction convert | - // convert = instr and - // bitOffset = Ints::mul(convert.getDerivation().getByteOffset(), 8) - // ) - // or - // // Converting to a derived class subtracts the offset of the base class. - // exists(ConvertToDerivedInstruction convert | - // convert = instr and - // bitOffset = Ints::neg(Ints::mul(convert.getDerivation().getByteOffset(), 8)) - // ) - // or - // // Converting to a virtual base class adds an unknown offset. - // instr instanceof ConvertToVirtualBaseInstruction and - // bitOffset = Ints::unknown() - // or - // REVIEW: In the C# IR, we should ignore the above types of conversion all together, - // since first of all they do not provide correct information (nothing is known - // for sure about heap allocated objects) and second of all even if we create a - // virtual memory model for the IR I don't think such conversions provide any meaningful - // information; - // Conversion to another pointer type propagates the source address. - exists(ConvertInstruction convert, Type resultType | + // Converting to a non-virtual base class adds the offset of the base class. + exists(ConvertToNonVirtualBaseInstruction convert | convert = instr and - resultType = convert.getResultType() and - ( - resultType instanceof PointerType or - resultType instanceof RefType - ) and + bitOffset = Ints::mul(convert.getDerivation().getByteOffset(), 8) + ) + or + // Converting to a derived class subtracts the offset of the base class. + exists(ConvertToDerivedInstruction convert | + convert = instr and + bitOffset = Ints::neg(Ints::mul(convert.getDerivation().getByteOffset(), 8)) + ) + or + // Converting to a virtual base class adds an unknown offset. + instr instanceof ConvertToVirtualBaseInstruction and + bitOffset = Ints::unknown() + or + // Conversion to another pointer type propagates the source address. + exists(ConvertInstruction convert, IRType resultType | + convert = instr and + resultType = convert.getResultIRType() and + resultType instanceof IRAddressType and bitOffset = 0 ) or // Adding an integer to or subtracting an integer from a pointer propagates // the address with an offset. - bitOffset = getPointerBitOffset(instr.(PointerOffsetInstruction)) + exists(PointerOffsetInstruction ptrOffset | + ptrOffset = instr and + operand = ptrOffset.getLeftOperand() and + bitOffset = getPointerBitOffset(ptrOffset) + ) or // Computing a field address from a pointer propagates the address plus the // offset of the field. - // TODO: Fix once class layout is synthesized - // bitOffset = Ints::unknown() - //or + bitOffset = Language::getFieldBitOffset(instr.(FieldAddressInstruction).getField()) + or // A copy propagates the source value. operand = instr.(CopyInstruction).getSourceValueOperand() and bitOffset = 0 or @@ -224,27 +193,42 @@ private predicate operandReturned(Operand operand, IntValue bitOffset) { } private predicate isArgumentForParameter(CallInstruction ci, Operand operand, Instruction init) { - exists(Callable c | + exists(Language::Function f | ci = operand.getUse() and - c = ci.getStaticCallTarget() and + f = ci.getStaticCallTarget() and ( - init.(InitializeParameterInstruction).getParameter() = c + init.(InitializeParameterInstruction).getParameter() = f .getParameter(operand.(PositionalArgumentOperand).getIndex()) or init instanceof InitializeThisInstruction and - init.getEnclosingFunction() = c and + init.getEnclosingFunction() = f and operand instanceof ThisArgumentOperand - ) // and - // not f.isVirtual() and - // not f instanceof AliasFunction + ) and + not Language::isFunctionVirtual(f) and + not f instanceof AliasModels::AliasFunction ) } -private predicate isAlwaysReturnedArgument(Operand operand) { none() } +private predicate isAlwaysReturnedArgument(Operand operand) { + exists(AliasModels::AliasFunction f | + f = operand.getUse().(CallInstruction).getStaticCallTarget() and + f.parameterIsAlwaysReturned(operand.(PositionalArgumentOperand).getIndex()) + ) +} -private predicate isOnlyEscapesViaReturnArgument(Operand operand) { none() } +private predicate isOnlyEscapesViaReturnArgument(Operand operand) { + exists(AliasModels::AliasFunction f | + f = operand.getUse().(CallInstruction).getStaticCallTarget() and + f.parameterEscapesOnlyViaReturn(operand.(PositionalArgumentOperand).getIndex()) + ) +} -private predicate isNeverEscapesArgument(Operand operand) { none() } +private predicate isNeverEscapesArgument(Operand operand) { + exists(AliasModels::AliasFunction f | + f = operand.getUse().(CallInstruction).getStaticCallTarget() and + f.parameterNeverEscapes(operand.(PositionalArgumentOperand).getIndex()) + ) +} private predicate resultReturned(Instruction instr, IntValue bitOffset) { operandReturned(instr.getAUse(), bitOffset) @@ -279,9 +263,14 @@ private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) { * analysis. */ predicate variableAddressEscapes(IRVariable var) { - automaticVariableAddressEscapes(var.(IRAutomaticVariable)) + exists(IREscapeAnalysisConfiguration config | + config.useSoundEscapeAnalysis() and + automaticVariableAddressEscapes(var.(IRAutomaticVariable)) + ) or - // All variables with static storage duration have their address escape. + // All variables with static storage duration have their address escape, even when escape analysis + // is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global + // variable. Normally, we rely on `AliasedDefinition` to handle that. not var instanceof IRAutomaticVariable } @@ -295,6 +284,10 @@ predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset) instr.(VariableAddressInstruction).getIRVariable() = var and bitOffset = 0 or + // A string literal is just a special read-only global variable. + instr.(StringConstantInstruction).getIRVariable() = var and + bitOffset = 0 + or exists(Operand operand, IntValue originalBitOffset, IntValue propagatedBitOffset | operand = instr.getAnOperand() and // If an operand is propagated, then the result points to the same variable, diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll new file mode 100644 index 00000000000..11d7d37063e --- /dev/null +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll @@ -0,0 +1,48 @@ +private import csharp +import semmle.code.csharp.ir.implementation.IRConfiguration +import semmle.code.csharp.ir.internal.IntegerConstant as Ints + +module AliasModels { + /** + * Models the aliasing behavior of a library function. + */ + abstract class AliasFunction extends Callable { + /** + * Holds if the address passed to the parameter at the specified index is never retained after + * the function returns. + * + * Example: + * ``` + * int* g; + * int* func(int* p, int* q, int* r, int* s, int n) { + * *s = 1; // `s` does not escape. + * g = p; // Stored in global. `p` escapes. + * if (rand()) { + * return q; // `q` escapes via the return value. + * } + * else { + * return r + n; // `r` escapes via the return value, even though an offset has been added. + * } + * } + * ``` + * + * For the above function, the following terms hold: + * - `parameterEscapesOnlyViaReturn(1)` + * - `parameterEscapesOnlyViaReturn(2)` + * - `parameterNeverEscapes(3)` + */ + abstract predicate parameterNeverEscapes(int index); + + /** + * Holds if the address passed to the parameter at the specified index escapes via the return + * value of the function, but does not otherwise escape. See the comment for + * `parameterNeverEscapes` for an example. + */ + abstract predicate parameterEscapesOnlyViaReturn(int index); + + /** + * Holds if the function always returns the value of the parameter at the specified index. + */ + abstract predicate parameterIsAlwaysReturned(int index); + } +} diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll index 64d671c76c4..59067636b40 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll @@ -1 +1,2 @@ +import semmle.code.csharp.ir.internal.IRCSharpLanguage as Language import semmle.code.csharp.ir.implementation.raw.IR as InputIR diff --git a/csharp/ql/src/semmle/code/csharp/ir/internal/IRCSharpLanguage.qll b/csharp/ql/src/semmle/code/csharp/ir/internal/IRCSharpLanguage.qll index 97121f46453..cd4e96a25a3 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/internal/IRCSharpLanguage.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/internal/IRCSharpLanguage.qll @@ -101,3 +101,16 @@ predicate hasPotentialLoop(Function f) { } predicate hasGoto(Function f) { exists(CSharp::GotoStmt s | s.getEnclosingCallable() = f) } + +/** + * Gets the offset of field `field` in bits. + */ +int getFieldBitOffset(Field f) { + //REVIEW: Implement this once layout has been synthesized. + none() +} + +/** + * Holds if the specified `Function` can be overridden in a derived class. + */ +predicate isFunctionVirtual(Function f) { f.(CSharp::Virtualizable).isOverridableOrImplementable() } From 1bbc875442d092474439aad26090b18576060f13 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 28 Jan 2020 10:51:21 -0700 Subject: [PATCH 066/148] C++/C#: Parameterize alias analysis based on `AliasConfiguration` Instead of tracking `IRVariable`s directly, alias analysis now tracks instances of the `Allocation` type provided by its `Configuration` parameter. For unaliased SSA, an `Allocation` is just an `IRAutomaticVariable`. For aliased SSA, an `Allocation` is either an `IRVariable` or the memory pointed to by an indirect parameter. --- .../aliased_ssa/internal/AliasAnalysis.qll | 121 +++++++++++------- .../internal/AliasAnalysisInternal.qll | 1 + .../internal/AliasConfiguration.qll | 97 ++++++++++++++ .../internal/AliasConfigurationInternal.qll | 1 + .../unaliased_ssa/internal/AliasAnalysis.qll | 121 +++++++++++------- .../internal/AliasAnalysisInternal.qll | 1 + .../internal/AliasConfiguration.qll | 16 +++ .../internal/AliasConfigurationImports.qll | 1 + .../unaliased_ssa/internal/AliasAnalysis.qll | 121 +++++++++++------- .../internal/AliasAnalysisInternal.qll | 1 + .../internal/AliasConfiguration.qll | 16 +++ .../internal/AliasConfigurationImports.qll | 1 + 12 files changed, 354 insertions(+), 144 deletions(-) create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfigurationInternal.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll create mode 100644 cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll create mode 100644 csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll create mode 100644 csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll index 3c6591404b2..6edca9ccc21 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll @@ -246,61 +246,86 @@ private predicate resultEscapesNonReturn(Instruction instr) { } /** - * Holds if the address of the specified local variable or parameter escapes the - * domain of the analysis. + * Holds if the address of `allocation` escapes outside the domain of the analysis. This can occur + * either because the allocation's address is taken within the function and escapes, or because the + * allocation is marked as always escaping via `alwaysEscapes()`. */ -private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) { - // The variable's address escapes if the result of any - // VariableAddressInstruction that computes the variable's address escapes. - exists(VariableAddressInstruction instr | - instr.getIRVariable() = var and - resultEscapesNonReturn(instr) - ) -} - -/** - * Holds if the address of the specified variable escapes the domain of the - * analysis. - */ -predicate variableAddressEscapes(IRVariable var) { +predicate allocationEscapes(Configuration::Allocation allocation) { + allocation.alwaysEscapes() + or exists(IREscapeAnalysisConfiguration config | - config.useSoundEscapeAnalysis() and - automaticVariableAddressEscapes(var.(IRAutomaticVariable)) + config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction()) ) - or - // All variables with static storage duration have their address escape, even when escape analysis - // is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global - // variable. Normally, we rely on `AliasedDefinition` to handle that. - not var instanceof IRAutomaticVariable } /** - * Holds if the result of instruction `instr` points within variable `var`, at - * bit offset `bitOffset` within the variable. If the result points within - * `var`, but at an unknown or non-constant offset, then `bitOffset` is unknown. + * Equivalent to `operandIsPropagated()`, but includes interprocedural propagation. */ -predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset) { - // The address of a variable points to that variable, at offset 0. - instr.(VariableAddressInstruction).getIRVariable() = var and - bitOffset = 0 +private predicate operandIsPropagatedIncludingByCall(Operand operand, IntValue bitOffset) { + operandIsPropagated(operand, bitOffset) or - // A string literal is just a special read-only global variable. - instr.(StringConstantInstruction).getIRVariable() = var and - bitOffset = 0 - or - exists(Operand operand, IntValue originalBitOffset, IntValue propagatedBitOffset | - operand = instr.getAnOperand() and - // If an operand is propagated, then the result points to the same variable, - // offset by the bit offset from the propagation. - resultPointsTo(operand.getAnyDef(), var, originalBitOffset) and - ( - operandIsPropagated(operand, propagatedBitOffset) - or - exists(CallInstruction ci, Instruction init | - isArgumentForParameter(ci, operand, init) and - resultReturned(init, propagatedBitOffset) - ) - ) and - bitOffset = Ints::add(originalBitOffset, propagatedBitOffset) + exists(CallInstruction call, Instruction init | + isArgumentForParameter(call, operand, init) and + resultReturned(init, bitOffset) + ) +} + +/** + * Holds if `addrOperand` is at offset `bitOffset` from the value of instruction `base`. The offset + * may be `unknown()`. + */ +private predicate hasBaseAndOffset(AddressOperand addrOperand, Instruction base, IntValue bitOffset) { + base = addrOperand.getDef() and bitOffset = 0 // Base case + or + exists( + Instruction middle, int previousBitOffset, Operand middleOperand, IntValue additionalBitOffset + | + // We already have an offset from `middle`. + hasBaseAndOffset(addrOperand, middle, previousBitOffset) and + // `middle` is propagated from `base`. + middleOperand = middle.getAnOperand() and + operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset) and + base = middleOperand.getDef() and + bitOffset = Ints::add(previousBitOffset, additionalBitOffset) + ) +} + +/** + * Holds if `addrOperand` is at constant offset `bitOffset` from the value of instruction `base`. + * Only holds for the `base` with the longest chain of propagation to `addrOperand`. + */ +predicate addressOperandBaseAndConstantOffset( + AddressOperand addrOperand, Instruction base, int bitOffset +) { + hasBaseAndOffset(addrOperand, base, bitOffset) and + Ints::hasValue(bitOffset) and + not exists(Instruction previousBase, int previousBitOffset | + hasBaseAndOffset(addrOperand, previousBase, previousBitOffset) and + previousBase = base.getAnOperand().getDef() and + Ints::hasValue(previousBitOffset) + ) +} + +/** + * Gets the allocation into which `addrOperand` points, if known. + */ +Configuration::Allocation getAddressOperandAllocation(AddressOperand addrOperand) { + addressOperandAllocationAndOffset(addrOperand, result, _) +} + +/** + * Holds if `addrOperand` is at offset `bitOffset` from a base instruction of `allocation`. The + * offset may be `unknown()`. + */ +predicate addressOperandAllocationAndOffset( + AddressOperand addrOperand, Configuration::Allocation allocation, IntValue bitOffset +) { + exists(Instruction base | + allocation.getABaseInstruction() = base and + hasBaseAndOffset(addrOperand, base, bitOffset) and + not exists(Instruction previousBase | + hasBaseAndOffset(addrOperand, previousBase, _) and + previousBase = base.getAnOperand().getDef() + ) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll index 80dbb888db7..8a407105080 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisInternal.qll @@ -1,2 +1,3 @@ import semmle.code.cpp.ir.internal.IRCppLanguage as Language import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as InputIR +import AliasConfiguration as Configuration diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll new file mode 100644 index 00000000000..d9937294d70 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll @@ -0,0 +1,97 @@ +private import AliasConfigurationInternal +private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR +private import cpp +private import AliasAnalysis + +private newtype TAllocation = + TVariableAllocation(IRVariable var) or + TIndirectParameterAllocation(IRAutomaticUserVariable var) { + exists(InitializeIndirectionInstruction instr | instr.getIRVariable() = var) + } + +/** + * A memory allocation that can be tracked by the AliasedSSA alias analysis. + */ +abstract class Allocation extends TAllocation { + abstract string toString(); + + final string getAllocationString() { result = toString() } + + abstract Instruction getABaseInstruction(); + + abstract IRFunction getEnclosingIRFunction(); + + abstract Language::Location getLocation(); + + abstract string getUniqueId(); + + abstract IRType getIRType(); + + abstract predicate isReadOnly(); + + abstract predicate alwaysEscapes(); + + abstract predicate isAlwaysAllocatedOnStack(); + + final predicate isUnaliased() { not allocationEscapes(this) } +} + +class VariableAllocation extends Allocation, TVariableAllocation { + IRVariable var; + + VariableAllocation() { this = TVariableAllocation(var) } + + final override string toString() { result = var.toString() } + + final override VariableInstruction getABaseInstruction() { + result.getIRVariable() = var and + (result instanceof VariableAddressInstruction or result instanceof StringConstantInstruction) + } + + final override IRFunction getEnclosingIRFunction() { result = var.getEnclosingIRFunction() } + + final override Language::Location getLocation() { result = var.getLocation() } + + final override string getUniqueId() { result = var.getUniqueId() } + + final override IRType getIRType() { result = var.getIRType() } + + final override predicate isReadOnly() { var.isReadOnly() } + + final override predicate isAlwaysAllocatedOnStack() { var instanceof IRAutomaticVariable } + + final override predicate alwaysEscapes() { + // All variables with static storage duration have their address escape, even when escape analysis + // is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global + // variable. Normally, we rely on `AliasedDefinition` to handle that. + not var instanceof IRAutomaticVariable + } + + final IRVariable getIRVariable() { result = var } +} + +class IndirectParameterAllocation extends Allocation, TIndirectParameterAllocation { + IRAutomaticUserVariable var; + + IndirectParameterAllocation() { this = TIndirectParameterAllocation(var) } + + final override string toString() { result = "*" + var.toString() } + + final override InitializeParameterInstruction getABaseInstruction() { + result.getIRVariable() = var + } + + final override IRFunction getEnclosingIRFunction() { result = var.getEnclosingIRFunction() } + + final override Language::Location getLocation() { result = var.getLocation() } + + final override string getUniqueId() { result = var.getUniqueId() } + + final override IRType getIRType() { result = var.getIRType() } + + final override predicate isReadOnly() { none() } + + final override predicate isAlwaysAllocatedOnStack() { none() } + + final override predicate alwaysEscapes() { none() } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfigurationInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfigurationInternal.qll new file mode 100644 index 00000000000..bd6c2f4c151 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfigurationInternal.qll @@ -0,0 +1 @@ +import semmle.code.cpp.ir.internal.IRCppLanguage as Language diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll index 3c6591404b2..6edca9ccc21 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll @@ -246,61 +246,86 @@ private predicate resultEscapesNonReturn(Instruction instr) { } /** - * Holds if the address of the specified local variable or parameter escapes the - * domain of the analysis. + * Holds if the address of `allocation` escapes outside the domain of the analysis. This can occur + * either because the allocation's address is taken within the function and escapes, or because the + * allocation is marked as always escaping via `alwaysEscapes()`. */ -private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) { - // The variable's address escapes if the result of any - // VariableAddressInstruction that computes the variable's address escapes. - exists(VariableAddressInstruction instr | - instr.getIRVariable() = var and - resultEscapesNonReturn(instr) - ) -} - -/** - * Holds if the address of the specified variable escapes the domain of the - * analysis. - */ -predicate variableAddressEscapes(IRVariable var) { +predicate allocationEscapes(Configuration::Allocation allocation) { + allocation.alwaysEscapes() + or exists(IREscapeAnalysisConfiguration config | - config.useSoundEscapeAnalysis() and - automaticVariableAddressEscapes(var.(IRAutomaticVariable)) + config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction()) ) - or - // All variables with static storage duration have their address escape, even when escape analysis - // is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global - // variable. Normally, we rely on `AliasedDefinition` to handle that. - not var instanceof IRAutomaticVariable } /** - * Holds if the result of instruction `instr` points within variable `var`, at - * bit offset `bitOffset` within the variable. If the result points within - * `var`, but at an unknown or non-constant offset, then `bitOffset` is unknown. + * Equivalent to `operandIsPropagated()`, but includes interprocedural propagation. */ -predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset) { - // The address of a variable points to that variable, at offset 0. - instr.(VariableAddressInstruction).getIRVariable() = var and - bitOffset = 0 +private predicate operandIsPropagatedIncludingByCall(Operand operand, IntValue bitOffset) { + operandIsPropagated(operand, bitOffset) or - // A string literal is just a special read-only global variable. - instr.(StringConstantInstruction).getIRVariable() = var and - bitOffset = 0 - or - exists(Operand operand, IntValue originalBitOffset, IntValue propagatedBitOffset | - operand = instr.getAnOperand() and - // If an operand is propagated, then the result points to the same variable, - // offset by the bit offset from the propagation. - resultPointsTo(operand.getAnyDef(), var, originalBitOffset) and - ( - operandIsPropagated(operand, propagatedBitOffset) - or - exists(CallInstruction ci, Instruction init | - isArgumentForParameter(ci, operand, init) and - resultReturned(init, propagatedBitOffset) - ) - ) and - bitOffset = Ints::add(originalBitOffset, propagatedBitOffset) + exists(CallInstruction call, Instruction init | + isArgumentForParameter(call, operand, init) and + resultReturned(init, bitOffset) + ) +} + +/** + * Holds if `addrOperand` is at offset `bitOffset` from the value of instruction `base`. The offset + * may be `unknown()`. + */ +private predicate hasBaseAndOffset(AddressOperand addrOperand, Instruction base, IntValue bitOffset) { + base = addrOperand.getDef() and bitOffset = 0 // Base case + or + exists( + Instruction middle, int previousBitOffset, Operand middleOperand, IntValue additionalBitOffset + | + // We already have an offset from `middle`. + hasBaseAndOffset(addrOperand, middle, previousBitOffset) and + // `middle` is propagated from `base`. + middleOperand = middle.getAnOperand() and + operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset) and + base = middleOperand.getDef() and + bitOffset = Ints::add(previousBitOffset, additionalBitOffset) + ) +} + +/** + * Holds if `addrOperand` is at constant offset `bitOffset` from the value of instruction `base`. + * Only holds for the `base` with the longest chain of propagation to `addrOperand`. + */ +predicate addressOperandBaseAndConstantOffset( + AddressOperand addrOperand, Instruction base, int bitOffset +) { + hasBaseAndOffset(addrOperand, base, bitOffset) and + Ints::hasValue(bitOffset) and + not exists(Instruction previousBase, int previousBitOffset | + hasBaseAndOffset(addrOperand, previousBase, previousBitOffset) and + previousBase = base.getAnOperand().getDef() and + Ints::hasValue(previousBitOffset) + ) +} + +/** + * Gets the allocation into which `addrOperand` points, if known. + */ +Configuration::Allocation getAddressOperandAllocation(AddressOperand addrOperand) { + addressOperandAllocationAndOffset(addrOperand, result, _) +} + +/** + * Holds if `addrOperand` is at offset `bitOffset` from a base instruction of `allocation`. The + * offset may be `unknown()`. + */ +predicate addressOperandAllocationAndOffset( + AddressOperand addrOperand, Configuration::Allocation allocation, IntValue bitOffset +) { + exists(Instruction base | + allocation.getABaseInstruction() = base and + hasBaseAndOffset(addrOperand, base, bitOffset) and + not exists(Instruction previousBase | + hasBaseAndOffset(addrOperand, previousBase, _) and + previousBase = base.getAnOperand().getDef() + ) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll index eeb64333bf2..08a563abc73 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll @@ -1,2 +1,3 @@ import semmle.code.cpp.ir.internal.IRCppLanguage as Language import semmle.code.cpp.ir.implementation.raw.IR as InputIR +import AliasConfiguration as Configuration diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll new file mode 100644 index 00000000000..5be476e12ee --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll @@ -0,0 +1,16 @@ +private import AliasConfigurationImports + +/** + * A memory allocation that can be tracked by the SimpleSSA alias analysis. + * All automatic variables are tracked. + */ +class Allocation extends IRAutomaticVariable { + VariableAddressInstruction getABaseInstruction() { result.getIRVariable() = this } + + final string getAllocationString() { result = toString() } + + predicate alwaysEscapes() { + // An automatic variable only escapes if its address is taken and escapes. + none() + } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll new file mode 100644 index 00000000000..07cbc6308b7 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll @@ -0,0 +1 @@ +import semmle.code.cpp.ir.implementation.raw.IR diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll index 3c6591404b2..6edca9ccc21 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll @@ -246,61 +246,86 @@ private predicate resultEscapesNonReturn(Instruction instr) { } /** - * Holds if the address of the specified local variable or parameter escapes the - * domain of the analysis. + * Holds if the address of `allocation` escapes outside the domain of the analysis. This can occur + * either because the allocation's address is taken within the function and escapes, or because the + * allocation is marked as always escaping via `alwaysEscapes()`. */ -private predicate automaticVariableAddressEscapes(IRAutomaticVariable var) { - // The variable's address escapes if the result of any - // VariableAddressInstruction that computes the variable's address escapes. - exists(VariableAddressInstruction instr | - instr.getIRVariable() = var and - resultEscapesNonReturn(instr) - ) -} - -/** - * Holds if the address of the specified variable escapes the domain of the - * analysis. - */ -predicate variableAddressEscapes(IRVariable var) { +predicate allocationEscapes(Configuration::Allocation allocation) { + allocation.alwaysEscapes() + or exists(IREscapeAnalysisConfiguration config | - config.useSoundEscapeAnalysis() and - automaticVariableAddressEscapes(var.(IRAutomaticVariable)) + config.useSoundEscapeAnalysis() and resultEscapesNonReturn(allocation.getABaseInstruction()) ) - or - // All variables with static storage duration have their address escape, even when escape analysis - // is allowed to be unsound. Otherwise, we won't have a definition for any non-escaped global - // variable. Normally, we rely on `AliasedDefinition` to handle that. - not var instanceof IRAutomaticVariable } /** - * Holds if the result of instruction `instr` points within variable `var`, at - * bit offset `bitOffset` within the variable. If the result points within - * `var`, but at an unknown or non-constant offset, then `bitOffset` is unknown. + * Equivalent to `operandIsPropagated()`, but includes interprocedural propagation. */ -predicate resultPointsTo(Instruction instr, IRVariable var, IntValue bitOffset) { - // The address of a variable points to that variable, at offset 0. - instr.(VariableAddressInstruction).getIRVariable() = var and - bitOffset = 0 +private predicate operandIsPropagatedIncludingByCall(Operand operand, IntValue bitOffset) { + operandIsPropagated(operand, bitOffset) or - // A string literal is just a special read-only global variable. - instr.(StringConstantInstruction).getIRVariable() = var and - bitOffset = 0 - or - exists(Operand operand, IntValue originalBitOffset, IntValue propagatedBitOffset | - operand = instr.getAnOperand() and - // If an operand is propagated, then the result points to the same variable, - // offset by the bit offset from the propagation. - resultPointsTo(operand.getAnyDef(), var, originalBitOffset) and - ( - operandIsPropagated(operand, propagatedBitOffset) - or - exists(CallInstruction ci, Instruction init | - isArgumentForParameter(ci, operand, init) and - resultReturned(init, propagatedBitOffset) - ) - ) and - bitOffset = Ints::add(originalBitOffset, propagatedBitOffset) + exists(CallInstruction call, Instruction init | + isArgumentForParameter(call, operand, init) and + resultReturned(init, bitOffset) + ) +} + +/** + * Holds if `addrOperand` is at offset `bitOffset` from the value of instruction `base`. The offset + * may be `unknown()`. + */ +private predicate hasBaseAndOffset(AddressOperand addrOperand, Instruction base, IntValue bitOffset) { + base = addrOperand.getDef() and bitOffset = 0 // Base case + or + exists( + Instruction middle, int previousBitOffset, Operand middleOperand, IntValue additionalBitOffset + | + // We already have an offset from `middle`. + hasBaseAndOffset(addrOperand, middle, previousBitOffset) and + // `middle` is propagated from `base`. + middleOperand = middle.getAnOperand() and + operandIsPropagatedIncludingByCall(middleOperand, additionalBitOffset) and + base = middleOperand.getDef() and + bitOffset = Ints::add(previousBitOffset, additionalBitOffset) + ) +} + +/** + * Holds if `addrOperand` is at constant offset `bitOffset` from the value of instruction `base`. + * Only holds for the `base` with the longest chain of propagation to `addrOperand`. + */ +predicate addressOperandBaseAndConstantOffset( + AddressOperand addrOperand, Instruction base, int bitOffset +) { + hasBaseAndOffset(addrOperand, base, bitOffset) and + Ints::hasValue(bitOffset) and + not exists(Instruction previousBase, int previousBitOffset | + hasBaseAndOffset(addrOperand, previousBase, previousBitOffset) and + previousBase = base.getAnOperand().getDef() and + Ints::hasValue(previousBitOffset) + ) +} + +/** + * Gets the allocation into which `addrOperand` points, if known. + */ +Configuration::Allocation getAddressOperandAllocation(AddressOperand addrOperand) { + addressOperandAllocationAndOffset(addrOperand, result, _) +} + +/** + * Holds if `addrOperand` is at offset `bitOffset` from a base instruction of `allocation`. The + * offset may be `unknown()`. + */ +predicate addressOperandAllocationAndOffset( + AddressOperand addrOperand, Configuration::Allocation allocation, IntValue bitOffset +) { + exists(Instruction base | + allocation.getABaseInstruction() = base and + hasBaseAndOffset(addrOperand, base, bitOffset) and + not exists(Instruction previousBase | + hasBaseAndOffset(addrOperand, previousBase, _) and + previousBase = base.getAnOperand().getDef() + ) ) } diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll index 59067636b40..e7884ce20b6 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysisInternal.qll @@ -1,2 +1,3 @@ import semmle.code.csharp.ir.internal.IRCSharpLanguage as Language import semmle.code.csharp.ir.implementation.raw.IR as InputIR +import AliasConfiguration as Configuration diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll new file mode 100644 index 00000000000..5be476e12ee --- /dev/null +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll @@ -0,0 +1,16 @@ +private import AliasConfigurationImports + +/** + * A memory allocation that can be tracked by the SimpleSSA alias analysis. + * All automatic variables are tracked. + */ +class Allocation extends IRAutomaticVariable { + VariableAddressInstruction getABaseInstruction() { result.getIRVariable() = this } + + final string getAllocationString() { result = toString() } + + predicate alwaysEscapes() { + // An automatic variable only escapes if its address is taken and escapes. + none() + } +} diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll new file mode 100644 index 00000000000..0db2b956610 --- /dev/null +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfigurationImports.qll @@ -0,0 +1 @@ +import semmle.code.csharp.ir.implementation.raw.IR From 165a45d9b5cfca92f35789d896845f856acbab8a Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 28 Jan 2020 10:53:18 -0700 Subject: [PATCH 067/148] C++/C#: Update SimpleSSA to use `Allocation` instead of `IRVariable` --- .../unaliased_ssa/internal/SimpleSSA.qll | 89 ++++++++----------- .../unaliased_ssa/internal/SimpleSSA.qll | 89 ++++++++----------- 2 files changed, 74 insertions(+), 104 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll index ddff6444b90..7de1ab8d72e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll @@ -1,66 +1,57 @@ import AliasAnalysis private import SimpleSSAImports import SimpleSSAPublicImports +private import AliasConfiguration -private class IntValue = Ints::IntValue; - -private predicate hasResultMemoryAccess( - Instruction instr, IRVariable var, Language::LanguageType type, IntValue bitOffset -) { - resultPointsTo(instr.getResultAddressOperand().getAnyDef(), var, bitOffset) and - type = instr.getResultLanguageType() -} - -private predicate hasOperandMemoryAccess( - MemoryOperand operand, IRVariable var, Language::LanguageType type, IntValue bitOffset -) { - resultPointsTo(operand.getAddressOperand().getAnyDef(), var, bitOffset) and - type = operand.getLanguageType() -} - -/** - * Holds if the specified variable should be modeled in SSA form. For unaliased SSA, we only model a variable if its - * address never escapes and all reads and writes of that variable access the entire variable using the original type - * of the variable. - */ -private predicate isVariableModeled(IRVariable var) { - not variableAddressEscapes(var) and - // There's no need to check for the right size. An `IRVariable` never has an `UnknownType`, so the test for - // `type = var.getType()` is sufficient. - forall(Instruction instr, Language::LanguageType type, IntValue bitOffset | - hasResultMemoryAccess(instr, var, type, bitOffset) and - not instr.hasResultMayMemoryAccess() - | +private predicate isTotalAccess(Allocation var, AddressOperand addrOperand, IRType type) { + exists(Instruction constantBase, int bitOffset | + addressOperandBaseAndConstantOffset(addrOperand, constantBase, bitOffset) and bitOffset = 0 and - type.getIRType() = var.getIRType() and - not instr.hasResultMayMemoryAccess() - ) and - forall(MemoryOperand operand, Language::LanguageType type, IntValue bitOffset | - hasOperandMemoryAccess(operand, var, type, bitOffset) - | - bitOffset = 0 and - type.getIRType() = var.getIRType() and - not operand.hasMayReadMemoryAccess() + constantBase = var.getABaseInstruction() and + type = var.getIRType() ) } -private newtype TMemoryLocation = MkMemoryLocation(IRVariable var) { isVariableModeled(var) } +/** + * Holds if the specified variable should be modeled in SSA form. For unaliased SSA, we only model a + * variable if its address never escapes and all reads and writes of that variable access the entire + * variable using the original type of the variable. + */ +private predicate isVariableModeled(Allocation var) { + not allocationEscapes(var) and + forall(Instruction instr, AddressOperand addrOperand, IRType type | + addrOperand = instr.getResultAddressOperand() and + type = instr.getResultIRType() and + var = getAddressOperandAllocation(addrOperand) + | + isTotalAccess(var, addrOperand, type) and not instr.hasResultMayMemoryAccess() + ) and + forall(MemoryOperand memOperand, AddressOperand addrOperand, IRType type | + addrOperand = memOperand.getAddressOperand() and + type = memOperand.getIRType() and + var = getAddressOperandAllocation(addrOperand) + | + isTotalAccess(var, addrOperand, type) and not memOperand.hasMayReadMemoryAccess() + ) +} -private MemoryLocation getMemoryLocation(IRVariable var) { result.getIRVariable() = var } +private newtype TMemoryLocation = MkMemoryLocation(Allocation var) { isVariableModeled(var) } + +private MemoryLocation getMemoryLocation(Allocation var) { result.getAllocation() = var } class MemoryLocation extends TMemoryLocation { - IRVariable var; + Allocation var; MemoryLocation() { this = MkMemoryLocation(var) } - final string toString() { result = var.toString() } + final string toString() { result = var.getAllocationString() } + + final Allocation getAllocation() { result = var } final Language::Location getLocation() { result = var.getLocation() } final IRFunction getIRFunction() { result = var.getEnclosingIRFunction() } - final IRVariable getIRVariable() { result = var } - final VirtualVariable getVirtualVariable() { result = this } final Language::LanguageType getType() { result = var.getLanguageType() } @@ -77,15 +68,9 @@ Overlap getOverlap(MemoryLocation def, MemoryLocation use) { } MemoryLocation getResultMemoryLocation(Instruction instr) { - exists(IRVariable var | - hasResultMemoryAccess(instr, var, _, _) and - result = getMemoryLocation(var) - ) + result = getMemoryLocation(getAddressOperandAllocation(instr.getResultAddressOperand())) } MemoryLocation getOperandMemoryLocation(MemoryOperand operand) { - exists(IRVariable var | - hasOperandMemoryAccess(operand, var, _, _) and - result = getMemoryLocation(var) - ) + result = getMemoryLocation(getAddressOperandAllocation(operand.getAddressOperand())) } diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll index ddff6444b90..7de1ab8d72e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll @@ -1,66 +1,57 @@ import AliasAnalysis private import SimpleSSAImports import SimpleSSAPublicImports +private import AliasConfiguration -private class IntValue = Ints::IntValue; - -private predicate hasResultMemoryAccess( - Instruction instr, IRVariable var, Language::LanguageType type, IntValue bitOffset -) { - resultPointsTo(instr.getResultAddressOperand().getAnyDef(), var, bitOffset) and - type = instr.getResultLanguageType() -} - -private predicate hasOperandMemoryAccess( - MemoryOperand operand, IRVariable var, Language::LanguageType type, IntValue bitOffset -) { - resultPointsTo(operand.getAddressOperand().getAnyDef(), var, bitOffset) and - type = operand.getLanguageType() -} - -/** - * Holds if the specified variable should be modeled in SSA form. For unaliased SSA, we only model a variable if its - * address never escapes and all reads and writes of that variable access the entire variable using the original type - * of the variable. - */ -private predicate isVariableModeled(IRVariable var) { - not variableAddressEscapes(var) and - // There's no need to check for the right size. An `IRVariable` never has an `UnknownType`, so the test for - // `type = var.getType()` is sufficient. - forall(Instruction instr, Language::LanguageType type, IntValue bitOffset | - hasResultMemoryAccess(instr, var, type, bitOffset) and - not instr.hasResultMayMemoryAccess() - | +private predicate isTotalAccess(Allocation var, AddressOperand addrOperand, IRType type) { + exists(Instruction constantBase, int bitOffset | + addressOperandBaseAndConstantOffset(addrOperand, constantBase, bitOffset) and bitOffset = 0 and - type.getIRType() = var.getIRType() and - not instr.hasResultMayMemoryAccess() - ) and - forall(MemoryOperand operand, Language::LanguageType type, IntValue bitOffset | - hasOperandMemoryAccess(operand, var, type, bitOffset) - | - bitOffset = 0 and - type.getIRType() = var.getIRType() and - not operand.hasMayReadMemoryAccess() + constantBase = var.getABaseInstruction() and + type = var.getIRType() ) } -private newtype TMemoryLocation = MkMemoryLocation(IRVariable var) { isVariableModeled(var) } +/** + * Holds if the specified variable should be modeled in SSA form. For unaliased SSA, we only model a + * variable if its address never escapes and all reads and writes of that variable access the entire + * variable using the original type of the variable. + */ +private predicate isVariableModeled(Allocation var) { + not allocationEscapes(var) and + forall(Instruction instr, AddressOperand addrOperand, IRType type | + addrOperand = instr.getResultAddressOperand() and + type = instr.getResultIRType() and + var = getAddressOperandAllocation(addrOperand) + | + isTotalAccess(var, addrOperand, type) and not instr.hasResultMayMemoryAccess() + ) and + forall(MemoryOperand memOperand, AddressOperand addrOperand, IRType type | + addrOperand = memOperand.getAddressOperand() and + type = memOperand.getIRType() and + var = getAddressOperandAllocation(addrOperand) + | + isTotalAccess(var, addrOperand, type) and not memOperand.hasMayReadMemoryAccess() + ) +} -private MemoryLocation getMemoryLocation(IRVariable var) { result.getIRVariable() = var } +private newtype TMemoryLocation = MkMemoryLocation(Allocation var) { isVariableModeled(var) } + +private MemoryLocation getMemoryLocation(Allocation var) { result.getAllocation() = var } class MemoryLocation extends TMemoryLocation { - IRVariable var; + Allocation var; MemoryLocation() { this = MkMemoryLocation(var) } - final string toString() { result = var.toString() } + final string toString() { result = var.getAllocationString() } + + final Allocation getAllocation() { result = var } final Language::Location getLocation() { result = var.getLocation() } final IRFunction getIRFunction() { result = var.getEnclosingIRFunction() } - final IRVariable getIRVariable() { result = var } - final VirtualVariable getVirtualVariable() { result = this } final Language::LanguageType getType() { result = var.getLanguageType() } @@ -77,15 +68,9 @@ Overlap getOverlap(MemoryLocation def, MemoryLocation use) { } MemoryLocation getResultMemoryLocation(Instruction instr) { - exists(IRVariable var | - hasResultMemoryAccess(instr, var, _, _) and - result = getMemoryLocation(var) - ) + result = getMemoryLocation(getAddressOperandAllocation(instr.getResultAddressOperand())) } MemoryLocation getOperandMemoryLocation(MemoryOperand operand) { - exists(IRVariable var | - hasOperandMemoryAccess(operand, var, _, _) and - result = getMemoryLocation(var) - ) + result = getMemoryLocation(getAddressOperandAllocation(operand.getAddressOperand())) } From 976b564b689203bdb2d67966c0fe222959a50274 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 28 Jan 2020 10:55:24 -0700 Subject: [PATCH 068/148] C++: Update AliasedSSA to use `Allocation` instead of `IRVariable` This introduces a new type of `MemoryLocation`: `EntireAllocationMemoryLocation`, representing an entire contiguous allocation whose size is not known. This is used to model the memory accesses on `InitializeIndirection` and `ReturnIndirection`. --- .../aliased_ssa/internal/AliasedSSA.qll | 205 ++++++++++++------ 1 file changed, 143 insertions(+), 62 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll index 324f0c16480..99fb22a5793 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll @@ -6,38 +6,52 @@ private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR private import semmle.code.cpp.ir.internal.IntegerConstant as Ints private import semmle.code.cpp.ir.internal.IntegerInterval as Interval private import semmle.code.cpp.ir.implementation.internal.OperandTag +private import AliasConfiguration private class IntValue = Ints::IntValue; +private predicate isIndirectOrBufferMemoryAccess(MemoryAccessKind kind) { + kind instanceof IndirectMemoryAccess or + kind instanceof BufferMemoryAccess +} + private predicate hasResultMemoryAccess( - Instruction instr, IRVariable var, IRType type, Language::LanguageType languageType, + Instruction instr, Allocation var, IRType type, Language::LanguageType languageType, IntValue startBitOffset, IntValue endBitOffset, boolean isMayAccess ) { - resultPointsTo(instr.getResultAddress(), var, startBitOffset) and - languageType = instr.getResultLanguageType() and - type = languageType.getIRType() and - (if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and - if exists(type.getByteSize()) - then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) - else endBitOffset = Ints::unknown() + exists(AddressOperand addrOperand | + addrOperand = instr.getResultAddressOperand() and + addressOperandAllocationAndOffset(addrOperand, var, startBitOffset) and + languageType = instr.getResultLanguageType() and + type = languageType.getIRType() and + isIndirectOrBufferMemoryAccess(instr.getResultMemoryAccess()) and + (if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and + if exists(type.getByteSize()) + then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) + else endBitOffset = Ints::unknown() + ) } private predicate hasOperandMemoryAccess( - MemoryOperand operand, IRVariable var, IRType type, Language::LanguageType languageType, + MemoryOperand operand, Allocation var, IRType type, Language::LanguageType languageType, IntValue startBitOffset, IntValue endBitOffset, boolean isMayAccess ) { - resultPointsTo(operand.getAddressOperand().getAnyDef(), var, startBitOffset) and - languageType = operand.getLanguageType() and - type = languageType.getIRType() and - (if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and - if exists(type.getByteSize()) - then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) - else endBitOffset = Ints::unknown() + exists(AddressOperand addrOperand | + addrOperand = operand.getAddressOperand() and + addressOperandAllocationAndOffset(addrOperand, var, startBitOffset) and + languageType = operand.getLanguageType() and + type = languageType.getIRType() and + isIndirectOrBufferMemoryAccess(operand.getMemoryAccess()) and + (if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and + if exists(type.getByteSize()) + then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) + else endBitOffset = Ints::unknown() + ) } private newtype TMemoryLocation = TVariableMemoryLocation( - IRVariable var, IRType type, Language::LanguageType languageType, IntValue startBitOffset, + Allocation var, IRType type, Language::LanguageType languageType, IntValue startBitOffset, IntValue endBitOffset, boolean isMayAccess ) { ( @@ -45,17 +59,18 @@ private newtype TMemoryLocation = or hasOperandMemoryAccess(_, var, type, _, startBitOffset, endBitOffset, isMayAccess) or - exists(IRAutomaticVariable autoVar | - // Always create a memory location for the entire variable. - autoVar = var and - type = autoVar.getIRType() and - startBitOffset = 0 and - endBitOffset = type.getByteSize() * 8 and - isMayAccess = false - ) + // For a stack variable, always create a memory location for the entire variable. + var.isAlwaysAllocatedOnStack() and + type = var.getIRType() and + startBitOffset = 0 and + endBitOffset = type.getByteSize() * 8 and + isMayAccess = false ) and languageType = type.getCanonicalLanguageType() } or + TEntireAllocationMemoryLocation(IndirectParameterAllocation var, boolean isMayAccess) { + isMayAccess = false or isMayAccess = true + } or TUnknownMemoryLocation(IRFunction irFunc, boolean isMayAccess) { isMayAccess = false or isMayAccess = true } or @@ -94,6 +109,8 @@ abstract class MemoryLocation extends TMemoryLocation { abstract predicate isMayAccess(); + Allocation getAllocation() { none() } + /** * Holds if the location cannot be overwritten except by definition of a `MemoryLocation` for * which `def.canDefineReadOnly()` holds. @@ -114,26 +131,63 @@ abstract class MemoryLocation extends TMemoryLocation { abstract class VirtualVariable extends MemoryLocation { } +abstract class AllocationMemoryLocation extends MemoryLocation { + Allocation var; + boolean isMayAccess; + + AllocationMemoryLocation() { + this instanceof TMemoryLocation and + isMayAccess = false + or + isMayAccess = true // Just ensures that `isMayAccess` is bound. + } + + final override VirtualVariable getVirtualVariable() { + if allocationEscapes(var) + then result = TAllAliasedMemory(var.getEnclosingIRFunction(), false) + else result.(AllocationMemoryLocation).getAllocation() = var + } + + final override IRFunction getIRFunction() { result = var.getEnclosingIRFunction() } + + final override Location getLocation() { result = var.getLocation() } + + final override Allocation getAllocation() { result = var } + + final override predicate isMayAccess() { isMayAccess = true } + + final override predicate isReadOnly() { var.isReadOnly() } +} + /** * An access to memory within a single known `IRVariable`. The variable may be either an unescaped variable * (with its own `VirtualIRVariable`) or an escaped variable (assigned to `UnknownVirtualVariable`). */ -class VariableMemoryLocation extends TVariableMemoryLocation, MemoryLocation { - IRVariable var; +class VariableMemoryLocation extends TVariableMemoryLocation, AllocationMemoryLocation { IRType type; Language::LanguageType languageType; IntValue startBitOffset; IntValue endBitOffset; - boolean isMayAccess; VariableMemoryLocation() { this = TVariableMemoryLocation(var, type, languageType, startBitOffset, endBitOffset, isMayAccess) } + private string getIntervalString() { + if coversEntireVariable() + then result = "" + else result = Interval::getIntervalString(startBitOffset, endBitOffset) + } + + private string getTypeString() { + if coversEntireVariable() and type = var.getIRType() + then result = "" + else result = "<" + languageType.toString() + ">" + } + final override string toStringInternal() { - result = var.toString() + Interval::getIntervalString(startBitOffset, endBitOffset) + "<" + - type.toString() + ", " + languageType.toString() + ">" + result = var.toString() + getIntervalString() + getTypeString() } final override Language::LanguageType getType() { @@ -151,34 +205,16 @@ class VariableMemoryLocation extends TVariableMemoryLocation, MemoryLocation { result = type.getCanonicalLanguageType() } - final override IRFunction getIRFunction() { result = var.getEnclosingIRFunction() } - - final override Location getLocation() { result = var.getLocation() } - final IntValue getStartBitOffset() { result = startBitOffset } final IntValue getEndBitOffset() { result = endBitOffset } - final IRVariable getVariable() { result = var } - final override string getUniqueId() { result = var.getUniqueId() + Interval::getIntervalString(startBitOffset, endBitOffset) + "<" + type.getIdentityString() + ">" } - final override VirtualVariable getVirtualVariable() { - if variableAddressEscapes(var) - then result = TAllAliasedMemory(var.getEnclosingIRFunction(), false) - else - result = TVariableMemoryLocation(var, var.getIRType(), _, 0, - var.getIRType().getByteSize() * 8, false) - } - - final override predicate isMayAccess() { isMayAccess = true } - - final override predicate isReadOnly() { var.isReadOnly() } - - final override predicate isAlwaysAllocatedOnStack() { var instanceof IRAutomaticVariable } + final override predicate isAlwaysAllocatedOnStack() { var.isAlwaysAllocatedOnStack() } /** * Holds if this memory location covers the entire variable. @@ -189,6 +225,26 @@ class VariableMemoryLocation extends TVariableMemoryLocation, MemoryLocation { } } +class EntireAllocationMemoryLocation extends TEntireAllocationMemoryLocation, + AllocationMemoryLocation { + EntireAllocationMemoryLocation() { this = TEntireAllocationMemoryLocation(var, isMayAccess) } + + final override string toStringInternal() { result = var.toString() } + + final override Language::LanguageType getType() { + result = any(IRUnknownType unknownType).getCanonicalLanguageType() + } + + final override string getUniqueId() { result = var.getUniqueId() } +} + +class EntireAllocationVirtualVariable extends EntireAllocationMemoryLocation, VirtualVariable { + EntireAllocationVirtualVariable() { + not allocationEscapes(var) and + not isMayAccess() + } +} + /** * Represents the `MemoryLocation` for an `IRVariable` that acts as its own `VirtualVariable`. Includes any * `VariableMemoryLocation` that exactly overlaps its entire `IRVariable`, and only if that `IRVariable` does not @@ -196,7 +252,7 @@ class VariableMemoryLocation extends TVariableMemoryLocation, MemoryLocation { */ class VariableVirtualVariable extends VariableMemoryLocation, VirtualVariable { VariableVirtualVariable() { - not variableAddressEscapes(var) and + not allocationEscapes(var) and type = var.getIRType() and coversEntireVariable() and not isMayAccess() @@ -354,13 +410,30 @@ private Overlap getExtentOverlap(MemoryLocation def, MemoryLocation use) { not use.isAlwaysAllocatedOnStack() ) or + def.getVirtualVariable() = use.getVirtualVariable() and + def instanceof EntireAllocationMemoryLocation and + ( + // EntireAllocationMemoryLocation exactly overlaps itself. + use instanceof EntireAllocationMemoryLocation and + result instanceof MustExactlyOverlap + or + // EntireAllocationMemoryLocation totally overlaps any location within the same virtual + // variable. + not use instanceof EntireAllocationMemoryLocation and + result instanceof MustTotallyOverlap + ) + or exists(VariableMemoryLocation defVariableLocation | defVariableLocation = def and ( // A VariableMemoryLocation may partially overlap an unknown location within the same // virtual variable. def.getVirtualVariable() = use.getVirtualVariable() and - (use instanceof UnknownMemoryLocation or use instanceof AllAliasedMemory) and + ( + use instanceof UnknownMemoryLocation or + use instanceof AllAliasedMemory or + use instanceof EntireAllocationMemoryLocation + ) and result instanceof MayPartiallyOverlap or // A VariableMemoryLocation that is not a local variable may partially overlap an @@ -421,19 +494,19 @@ private predicate isRelatableMemoryLocation(VariableMemoryLocation vml) { vml.getStartBitOffset() != Ints::unknown() } -private predicate isCoveredOffset(IRVariable var, int offsetRank, VariableMemoryLocation vml) { +private predicate isCoveredOffset(Allocation var, int offsetRank, VariableMemoryLocation vml) { exists(int startRank, int endRank, VirtualVariable vvar | vml.getStartBitOffset() = rank[startRank](IntValue offset_ | isRelevantOffset(vvar, offset_)) and vml.getEndBitOffset() = rank[endRank](IntValue offset_ | isRelevantOffset(vvar, offset_)) and - var = vml.getVariable() and + var = vml.getAllocation() and vvar = vml.getVirtualVariable() and isRelatableMemoryLocation(vml) and offsetRank in [startRank .. endRank] ) } -private predicate hasUnknownOffset(IRVariable var, VariableMemoryLocation vml) { - vml.getVariable() = var and +private predicate hasUnknownOffset(Allocation var, VariableMemoryLocation vml) { + vml.getAllocation() = var and ( vml.getStartBitOffset() = Ints::unknown() or vml.getEndBitOffset() = Ints::unknown() @@ -443,14 +516,14 @@ private predicate hasUnknownOffset(IRVariable var, VariableMemoryLocation vml) { private predicate overlappingIRVariableMemoryLocations( VariableMemoryLocation def, VariableMemoryLocation use ) { - exists(IRVariable var, int offsetRank | + exists(Allocation var, int offsetRank | isCoveredOffset(var, offsetRank, def) and isCoveredOffset(var, offsetRank, use) ) or - hasUnknownOffset(use.getVariable(), def) + hasUnknownOffset(use.getAllocation(), def) or - hasUnknownOffset(def.getVariable(), use) + hasUnknownOffset(def.getAllocation(), use) } private Overlap getVariableMemoryLocationOverlap( @@ -467,16 +540,20 @@ MemoryLocation getResultMemoryLocation(Instruction instr) { (if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and ( ( - kind.usesAddressOperand() and + isIndirectOrBufferMemoryAccess(kind) and if hasResultMemoryAccess(instr, _, _, _, _, _, _) then - exists(IRVariable var, IRType type, IntValue startBitOffset, IntValue endBitOffset | + exists(Allocation var, IRType type, IntValue startBitOffset, IntValue endBitOffset | hasResultMemoryAccess(instr, var, type, _, startBitOffset, endBitOffset, isMayAccess) and result = TVariableMemoryLocation(var, type, _, startBitOffset, endBitOffset, isMayAccess) ) else result = TUnknownMemoryLocation(instr.getEnclosingIRFunction(), isMayAccess) ) or + kind instanceof EntireAllocationMemoryAccess and + result = TEntireAllocationMemoryLocation(getAddressOperandAllocation(instr + .getResultAddressOperand()), isMayAccess) + or kind instanceof EscapedMemoryAccess and result = TAllAliasedMemory(instr.getEnclosingIRFunction(), isMayAccess) or @@ -492,16 +569,20 @@ MemoryLocation getOperandMemoryLocation(MemoryOperand operand) { (if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and ( ( - kind.usesAddressOperand() and + isIndirectOrBufferMemoryAccess(kind) and if hasOperandMemoryAccess(operand, _, _, _, _, _, _) then - exists(IRVariable var, IRType type, IntValue startBitOffset, IntValue endBitOffset | + exists(Allocation var, IRType type, IntValue startBitOffset, IntValue endBitOffset | hasOperandMemoryAccess(operand, var, type, _, startBitOffset, endBitOffset, isMayAccess) and result = TVariableMemoryLocation(var, type, _, startBitOffset, endBitOffset, isMayAccess) ) else result = TUnknownMemoryLocation(operand.getEnclosingIRFunction(), isMayAccess) ) or + kind instanceof EntireAllocationMemoryAccess and + result = TEntireAllocationMemoryLocation(getAddressOperandAllocation(operand + .getAddressOperand()), isMayAccess) + or kind instanceof EscapedMemoryAccess and result = TAllAliasedMemory(operand.getEnclosingIRFunction(), isMayAccess) or From d12b140921e2bed92c68137e28fc0b0f9b21415d Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 28 Jan 2020 10:55:38 -0700 Subject: [PATCH 069/148] C++/C#: Update shared file list --- config/identical-files.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/config/identical-files.json b/config/identical-files.json index e57bf00ca23..c93a2b28ae6 100644 --- a/config/identical-files.json +++ b/config/identical-files.json @@ -190,9 +190,14 @@ "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstructionImports.qll", "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstructionImports.qll" ], - "C++ SSA AliasAnalysis": [ + "SSA AliasAnalysis": [ "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll", - "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll" + "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll", + "csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll" + ], + "C++ SSA AliasAnalysisImports": [ + "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysisImports.qll", + "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysisImports.qll" ], "C++ IR ValueNumberingImports": [ "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/internal/ValueNumberingImports.qll", @@ -203,6 +208,10 @@ "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll", "csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SimpleSSA.qll" ], + "IR AliasConfiguration (unaliased_ssa)": [ + "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll", + "csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasConfiguration.qll" + ], "IR SSA SSAConstruction": [ "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll", "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll", From af9d90cf460666a85d34fb6f33733b622091e5f0 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 28 Jan 2020 10:56:13 -0700 Subject: [PATCH 070/148] C++: New test framework that allows expected results as comments in source code --- .../TestUtilities/InlineExpectationsTest.qll | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 cpp/ql/test/TestUtilities/InlineExpectationsTest.qll diff --git a/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll b/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll new file mode 100644 index 00000000000..8c253b194e4 --- /dev/null +++ b/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll @@ -0,0 +1,168 @@ +import cpp + +abstract class InlineExpectationsTest extends string { + bindingset[this] + InlineExpectationsTest() { any() } + + abstract string getARelevantTag(); + + abstract predicate hasActualResult(Location location, string element, string tag, string value); + + final predicate hasFailureMessage(FailureLocatable element, string message) { + exists(ActualResult actualResult | + actualResult.getTest() = this and + element = actualResult and + ( + exists(FalseNegativeExpectation falseNegative | + falseNegative.matchesActualResult(actualResult) and + message = "Fixed false negative:" + falseNegative.getExpectationText() + ) + or + not exists(ValidExpectation expectation | expectation.matchesActualResult(actualResult)) and + message = "Unexpected result: " + actualResult.getExpectationText() + ) + ) + or + exists(ValidExpectation expectation | + not exists(ActualResult actualResult | expectation.matchesActualResult(actualResult)) and + expectation.getTag() = getARelevantTag() and + element = expectation and + ( + expectation instanceof GoodExpectation and + message = "Missing result:" + expectation.getExpectationText() + or + expectation instanceof FalsePositiveExpectation and + message = "Fixed false positive:" + expectation.getExpectationText() + ) + ) + or + exists(InvalidExpectation expectation | + element = expectation and + message = "Invalid expectation syntax: " + expectation.getExpectation() + ) + } +} + +private string expectationCommentPattern() { result = "//\\s*(\\$(?:[^/]|/[^/])*)(?://.*)?" } + +private string expectationPattern() { + result = "(?:(f(?:\\+|-)):)?((?:[A-Za-z-_]+)(?:\\s*,\\s*[A-Za-z-_]+)*)(?:=(.*))?" +} + +private string getAnExpectation(CppStyleComment comment) { + result = comment.getContents().regexpCapture(expectationCommentPattern(), 1).splitAt("$").trim() and + result != "" +} + +private newtype TFailureLocatable = + TActualResult( + InlineExpectationsTest test, Location location, string element, string tag, string value + ) { + test.hasActualResult(location, element, tag, value) + } or + TValidExpectation(CppStyleComment comment, string tag, string value, string knownFailure) { + exists(string expectation | + expectation = getAnExpectation(comment) and + expectation.regexpMatch(expectationPattern()) and + tag = expectation.regexpCapture(expectationPattern(), 2).splitAt(",").trim() and + ( + if exists(expectation.regexpCapture(expectationPattern(), 3)) + then value = expectation.regexpCapture(expectationPattern(), 3) + else value = "" + ) and + ( + if exists(expectation.regexpCapture(expectationPattern(), 1)) + then knownFailure = expectation.regexpCapture(expectationPattern(), 1) + else knownFailure = "" + ) + ) + } or + TInvalidExpectation(CppStyleComment comment, string expectation) { + expectation = getAnExpectation(comment) and + not expectation.regexpMatch(expectationPattern()) + } + +class FailureLocatable extends TFailureLocatable { + string toString() { none() } + + Location getLocation() { none() } + + final string getExpectationText() { result = getTag() + "=" + getValue() } + + string getTag() { none() } + + string getValue() { none() } +} + +class ActualResult extends FailureLocatable, TActualResult { + InlineExpectationsTest test; + Location location; + string element; + string tag; + string value; + + ActualResult() { this = TActualResult(test, location, element, tag, value) } + + override string toString() { result = element } + + override Location getLocation() { result = location } + + InlineExpectationsTest getTest() { result = test } + + override string getTag() { result = tag } + + override string getValue() { result = value } +} + +abstract private class Expectation extends FailureLocatable { + CppStyleComment comment; + + override string toString() { result = comment.toString() } + + override Location getLocation() { result = comment.getLocation() } +} + +private class ValidExpectation extends Expectation, TValidExpectation { + string tag; + string value; + string knownFailure; + + ValidExpectation() { this = TValidExpectation(comment, tag, value, knownFailure) } + + override string getTag() { result = tag } + + override string getValue() { result = value } + + string getKnownFailure() { result = knownFailure } + + predicate matchesActualResult(ActualResult actualResult) { + getLocation().getStartLine() = actualResult.getLocation().getStartLine() and + getLocation().getFile() = actualResult.getLocation().getFile() and + getTag() = actualResult.getTag() and + getValue() = actualResult.getValue() + } +} + +class GoodExpectation extends ValidExpectation { + GoodExpectation() { getKnownFailure() = "" } +} + +class FalsePositiveExpectation extends ValidExpectation { + FalsePositiveExpectation() { getKnownFailure() = "f+" } +} + +class FalseNegativeExpectation extends ValidExpectation { + FalseNegativeExpectation() { getKnownFailure() = "f-" } +} + +class InvalidExpectation extends Expectation, TInvalidExpectation { + string expectation; + + InvalidExpectation() { this = TInvalidExpectation(comment, expectation) } + + string getExpectation() { result = expectation } +} + +query predicate failures(FailureLocatable element, string message) { + exists(InlineExpectationsTest test | test.hasFailureMessage(element, message)) +} From bb9485d548e45fa0b01ce6a3b8b4e2e8bb23ffd2 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 28 Jan 2020 10:56:49 -0700 Subject: [PATCH 071/148] C++: Update points_to tests to use new framework --- .../ir/escape/points_to.expected | 86 ------------------ .../test/library-tests/ir/escape/points_to.ql | 35 -------- .../library-tests/ir/points_to/points_to.cpp | 87 +++++++++++++++++++ .../ir/points_to/points_to.expected | 0 .../library-tests/ir/points_to/points_to.ql | 65 ++++++++++++++ 5 files changed, 152 insertions(+), 121 deletions(-) delete mode 100644 cpp/ql/test/library-tests/ir/escape/points_to.expected delete mode 100644 cpp/ql/test/library-tests/ir/escape/points_to.ql create mode 100644 cpp/ql/test/library-tests/ir/points_to/points_to.cpp create mode 100644 cpp/ql/test/library-tests/ir/points_to/points_to.expected create mode 100644 cpp/ql/test/library-tests/ir/points_to/points_to.ql diff --git a/cpp/ql/test/library-tests/ir/escape/points_to.expected b/cpp/ql/test/library-tests/ir/escape/points_to.expected deleted file mode 100644 index 6447bd81a5f..00000000000 --- a/cpp/ql/test/library-tests/ir/escape/points_to.expected +++ /dev/null @@ -1,86 +0,0 @@ -| escape.cpp:111:18:111:21 | CopyValue | no_+0 | no_+0 | -| escape.cpp:115:19:115:28 | PointerAdd[4] | no_+0 | no_+0 | -| escape.cpp:115:20:115:23 | CopyValue | no_+0 | no_+0 | -| escape.cpp:116:19:116:28 | PointerSub[4] | no_+0 | no_+0 | -| escape.cpp:116:20:116:23 | CopyValue | no_+0 | no_+0 | -| escape.cpp:117:19:117:26 | PointerAdd[4] | no_+0 | no_+0 | -| escape.cpp:117:23:117:26 | CopyValue | no_+0 | no_+0 | -| escape.cpp:118:9:118:12 | CopyValue | no_+0 | no_+0 | -| escape.cpp:120:12:120:15 | CopyValue | no_+0 | no_+0 | -| escape.cpp:123:14:123:17 | CopyValue | no_+0 | no_+0 | -| escape.cpp:124:15:124:18 | CopyValue | no_+0 | no_+0 | -| escape.cpp:127:9:127:12 | CopyValue | no_+0 | no_+0 | -| escape.cpp:129:12:129:15 | CopyValue | no_+0 | no_+0 | -| escape.cpp:134:5:134:18 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:134:11:134:18 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:135:5:135:12 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:135:5:135:15 | PointerAdd[4] | no_Array+20 | no_Array+20 | -| escape.cpp:136:5:136:15 | PointerAdd[4] | no_Array+20 | no_Array+20 | -| escape.cpp:136:7:136:14 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:137:17:137:24 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:137:17:137:27 | PointerAdd[4] | no_Array+20 | no_Array+20 | -| escape.cpp:138:17:138:27 | PointerAdd[4] | no_Array+20 | no_Array+20 | -| escape.cpp:138:19:138:26 | Convert | no_Array+0 | no_Array+0 | -| escape.cpp:140:21:140:32 | FieldAddress[x] | no_Point+0 | no_Point+0 | -| escape.cpp:140:21:140:32 | FieldAddress[y] | no_Point+4 | no_Point+4 | -| escape.cpp:140:21:140:32 | FieldAddress[z] | no_Point+8 | no_Point+8 | -| escape.cpp:141:27:141:27 | FieldAddress[x] | no_Point+0 | no_Point+0 | -| escape.cpp:142:14:142:14 | FieldAddress[y] | no_Point+4 | no_Point+4 | -| escape.cpp:143:19:143:27 | CopyValue | no_Point+0 | no_Point+0 | -| escape.cpp:143:31:143:31 | FieldAddress[y] | no_Point+4 | no_Point+4 | -| escape.cpp:144:6:144:14 | CopyValue | no_Point+0 | no_Point+0 | -| escape.cpp:144:18:144:18 | FieldAddress[y] | no_Point+4 | no_Point+4 | -| escape.cpp:145:20:145:30 | CopyValue | no_Point+8 | no_Point+8 | -| escape.cpp:145:30:145:30 | FieldAddress[z] | no_Point+8 | no_Point+8 | -| escape.cpp:146:5:146:18 | CopyValue | no_Point+8 | no_Point+8 | -| escape.cpp:146:7:146:17 | CopyValue | no_Point+8 | no_Point+8 | -| escape.cpp:146:17:146:17 | FieldAddress[z] | no_Point+8 | no_Point+8 | -| escape.cpp:149:5:149:14 | ConvertToNonVirtualBase[Derived : Intermediate1] | no_Derived+0 | no_Derived+0 | -| escape.cpp:149:5:149:14 | ConvertToNonVirtualBase[Intermediate1 : Base] | no_Derived+0 | no_Derived+0 | -| escape.cpp:149:16:149:16 | FieldAddress[b] | no_Derived+0 | no_Derived+0 | -| escape.cpp:150:18:150:27 | ConvertToNonVirtualBase[Derived : Intermediate1] | no_Derived+0 | no_Derived+0 | -| escape.cpp:150:18:150:27 | ConvertToNonVirtualBase[Intermediate1 : Base] | no_Derived+0 | no_Derived+0 | -| escape.cpp:150:29:150:29 | FieldAddress[b] | no_Derived+0 | no_Derived+0 | -| escape.cpp:151:5:151:14 | ConvertToNonVirtualBase[Derived : Intermediate2] | no_Derived+12 | no_Derived+12 | -| escape.cpp:151:16:151:17 | FieldAddress[i2] | no_Derived+16 | no_Derived+16 | -| escape.cpp:152:19:152:28 | ConvertToNonVirtualBase[Derived : Intermediate2] | no_Derived+12 | no_Derived+12 | -| escape.cpp:152:30:152:31 | FieldAddress[i2] | no_Derived+16 | no_Derived+16 | -| escape.cpp:155:17:155:30 | CopyValue | no_ssa_addrOf+0 | no_ssa_addrOf+0 | -| escape.cpp:155:17:155:30 | Store | no_ssa_addrOf+0 | no_ssa_addrOf+0 | -| escape.cpp:158:17:158:28 | CopyValue | no_ssa_refTo+0 | no_ssa_refTo+0 | -| escape.cpp:158:17:158:28 | Store | no_ssa_refTo+0 | no_ssa_refTo+0 | -| escape.cpp:161:19:161:42 | Convert | no_ssa_refToArrayElement+0 | no_ssa_refToArrayElement+0 | -| escape.cpp:161:19:161:45 | CopyValue | no_ssa_refToArrayElement+20 | no_ssa_refToArrayElement+20 | -| escape.cpp:161:19:161:45 | PointerAdd[4] | no_ssa_refToArrayElement+20 | no_ssa_refToArrayElement+20 | -| escape.cpp:161:19:161:45 | Store | no_ssa_refToArrayElement+20 | no_ssa_refToArrayElement+20 | -| escape.cpp:164:24:164:40 | CopyValue | no_ssa_refToArray+0 | no_ssa_refToArray+0 | -| escape.cpp:164:24:164:40 | Store | no_ssa_refToArray+0 | no_ssa_refToArray+0 | -| escape.cpp:167:19:167:28 | CopyValue | passByPtr+0 | passByPtr+0 | -| escape.cpp:170:21:170:29 | CopyValue | passByRef+0 | passByRef+0 | -| escape.cpp:173:22:173:38 | CopyValue | no_ssa_passByPtr+0 | no_ssa_passByPtr+0 | -| escape.cpp:176:24:176:39 | CopyValue | no_ssa_passByRef+0 | no_ssa_passByRef+0 | -| escape.cpp:179:22:179:42 | CopyValue | no_ssa_passByPtr_ret+0 | no_ssa_passByPtr_ret+0 | -| escape.cpp:182:24:182:43 | CopyValue | no_ssa_passByRef_ret+0 | no_ssa_passByRef_ret+0 | -| escape.cpp:185:30:185:40 | CopyValue | passByPtr2+0 | passByPtr2+0 | -| escape.cpp:188:32:188:41 | CopyValue | passByRef2+0 | passByRef2+0 | -| escape.cpp:191:30:191:42 | Call | none | passByPtr3+0 | -| escape.cpp:191:44:191:54 | CopyValue | passByPtr3+0 | passByPtr3+0 | -| escape.cpp:194:32:194:46 | Call | none | passByRef3+0 | -| escape.cpp:194:32:194:59 | CopyValue | none | passByRef3+0 | -| escape.cpp:194:48:194:57 | CopyValue | passByRef3+0 | passByRef3+0 | -| escape.cpp:199:17:199:34 | CopyValue | no_ssa_passByPtr4+0 | no_ssa_passByPtr4+0 | -| escape.cpp:199:37:199:54 | CopyValue | no_ssa_passByPtr5+0 | no_ssa_passByPtr5+0 | -| escape.cpp:202:5:202:19 | Call | none | passByRef6+0 | -| escape.cpp:202:5:202:32 | CopyValue | none | passByRef6+0 | -| escape.cpp:202:21:202:30 | CopyValue | passByRef6+0 | passByRef6+0 | -| escape.cpp:205:5:205:19 | Call | none | no_ssa_passByRef7+0 | -| escape.cpp:205:5:205:39 | CopyValue | none | no_ssa_passByRef7+0 | -| escape.cpp:205:21:205:37 | CopyValue | no_ssa_passByRef7+0 | no_ssa_passByRef7+0 | -| escape.cpp:209:14:209:25 | Call | none | no_ssa_c+0 | -| escape.cpp:217:14:217:16 | CopyValue | c2+0 | c2+0 | -| escape.cpp:221:8:221:19 | Call | none | c3+0 | -| escape.cpp:225:17:225:28 | Call | none | c4+0 | -| escape.cpp:247:2:247:27 | Store | condEscape1+0 | condEscape1+0 | -| escape.cpp:247:16:247:27 | CopyValue | condEscape1+0 | condEscape1+0 | -| escape.cpp:249:9:249:34 | Store | condEscape2+0 | condEscape2+0 | -| escape.cpp:249:23:249:34 | CopyValue | condEscape2+0 | condEscape2+0 | diff --git a/cpp/ql/test/library-tests/ir/escape/points_to.ql b/cpp/ql/test/library-tests/ir/escape/points_to.ql deleted file mode 100644 index 7c265974b10..00000000000 --- a/cpp/ql/test/library-tests/ir/escape/points_to.ql +++ /dev/null @@ -1,35 +0,0 @@ -import default -import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.AliasAnalysis as RawAA -import semmle.code.cpp.ir.implementation.raw.IR as Raw -import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasAnalysis as UnAA -import semmle.code.cpp.ir.implementation.unaliased_ssa.IR as Un -import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SSAConstruction -import semmle.code.cpp.ir.internal.IntegerConstant - -from Raw::Instruction rawInstr, Un::Instruction unInstr, string rawPointsTo, string unPointsTo -where - rawInstr = getOldInstruction(unInstr) and - not rawInstr instanceof Raw::VariableAddressInstruction and - ( - exists(Variable var, int rawBitOffset, int unBitOffset | - RawAA::resultPointsTo(rawInstr, Raw::getIRUserVariable(_, var), rawBitOffset) and - rawPointsTo = var.toString() + getBitOffsetString(rawBitOffset) and - UnAA::resultPointsTo(unInstr, Un::getIRUserVariable(_, var), unBitOffset) and - unPointsTo = var.toString() + getBitOffsetString(unBitOffset) - ) - or - exists(Variable var, int unBitOffset | - not RawAA::resultPointsTo(rawInstr, Raw::getIRUserVariable(_, var), _) and - rawPointsTo = "none" and - UnAA::resultPointsTo(unInstr, Un::getIRUserVariable(_, var), unBitOffset) and - unPointsTo = var.toString() + getBitOffsetString(unBitOffset) - ) - or - exists(Variable var, int rawBitOffset | - RawAA::resultPointsTo(rawInstr, Raw::getIRUserVariable(_, var), rawBitOffset) and - rawPointsTo = var.toString() + getBitOffsetString(rawBitOffset) and - not UnAA::resultPointsTo(unInstr, Un::getIRUserVariable(_, var), _) and - unPointsTo = "none" - ) - ) -select rawInstr.getLocation().toString(), rawInstr.getOperationString(), rawPointsTo, unPointsTo diff --git a/cpp/ql/test/library-tests/ir/points_to/points_to.cpp b/cpp/ql/test/library-tests/ir/points_to/points_to.cpp new file mode 100644 index 00000000000..249f08352ab --- /dev/null +++ b/cpp/ql/test/library-tests/ir/points_to/points_to.cpp @@ -0,0 +1,87 @@ +struct Point { + int x; + int y; +}; + +struct Base1 { + int b1; +}; + +struct Base2 { + int b2; +}; + +struct DerivedSI : Base1 { + int dsi; +}; + +struct DerivedMI : Base1, Base2 { + int dmi; +}; + +struct DerivedVI : virtual Base1 { + int dvi; +}; + +void Locals() { + Point pt = { //$ussa=pt + 1, //$ussa=pt[0..4) + 2 //$ussa=pt[4..8) + }; + int i = pt.x; //$ussa=pt[0..4) + i = pt.y; //$ussa=pt[4..8) + int* p = &pt.x; + i = *p; //$ussa=pt[0..4) + p = &pt.y; + i = *p; //$ussa=pt[4..8) +} + +void PointsTo( + int a, //$raw,ussa=a + Point& b, //$raw,ussa=b $ussa=*b + Point* c, //$raw,ussa=c $ussa=*c + int* d, //$raw,ussa=d $ussa=*d + DerivedSI* e, //$raw,ussa=e $ussa=*e + DerivedMI* f, //$raw,ussa=f $ussa=*f + DerivedVI* g //$raw,ussa=g $ussa=*g +) { + + int i = a; //$raw,ussa=a + i = *&a; //$raw,ussa=a + i = *(&a + 0); //$raw,ussa=a + i = b.x; //$raw,ussa=b $ussa=*b[0..4) + i = b.y; //$raw,ussa=b $ussa=*b[4..8) + i = c->x; //$raw,ussa=c $ussa=*c[0..4) + i = c->y; //$raw,ussa=c $ussa=*c[4..8) + i = *d; //$raw,ussa=d $ussa=*d[0..4) + i = *(d + 0); //$raw,ussa=d $ussa=*d[0..4) + i = d[5]; //$raw,ussa=d $ussa=*d[20..24) + i = 5[d]; //$raw,ussa=d $ussa=*d[20..24) + i = d[a]; //$raw,ussa=d $raw,ussa=a $ussa=*d[?..?) + i = a[d]; //$raw,ussa=d $raw,ussa=a $ussa=*d[?..?) + + int* p = &b.x; //$raw,ussa=b + i = *p; //$ussa=*b[0..4) + p = &b.y; //$raw,ussa=b + i = *p; //$ussa=*b[4..8) + p = &c->x; //$raw,ussa=c + i = *p; //$ussa=*c[0..4) + p = &c->y; //$raw,ussa=c + i = *p; //$ussa=*c[4..8) + p = &d[5]; //$raw,ussa=d + i = *p; //$ussa=*d[20..24) + p = &d[a]; //$raw,ussa=d $raw,ussa=a + i = *p; //$ussa=*d[?..?) + + Point* q = &c[a]; //$raw,ussa=c $raw,ussa=a + i = q->x; //$ussa=*c[?..?) + i = q->y; //$ussa=*c[?..?) + + i = e->b1; //$raw,ussa=e $ussa=*e[0..4) + i = e->dsi; //$raw,ussa=e $ussa=*e[4..8) + i = f->b1; //$raw,ussa=f $ussa=*f[0..4) + i = f->b2; //$raw,ussa=f $ussa=*f[4..8) + i = f->dmi; //$raw,ussa=f $ussa=*f[8..12) + i = g->b1; //$raw,ussa=g $ussa=*g[?..?) + i = g->dvi; //$raw,ussa=g $ussa=*g[8..12) +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/points_to/points_to.expected b/cpp/ql/test/library-tests/ir/points_to/points_to.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/cpp/ql/test/library-tests/ir/points_to/points_to.ql b/cpp/ql/test/library-tests/ir/points_to/points_to.ql new file mode 100644 index 00000000000..89d1e31e119 --- /dev/null +++ b/cpp/ql/test/library-tests/ir/points_to/points_to.ql @@ -0,0 +1,65 @@ +import cpp +private import TestUtilities.InlineExpectationsTest +private import semmle.code.cpp.ir.internal.IntegerConstant as Ints + +private predicate ignoreAllocation(string name) { + name = "i" or + name = "p" or + name = "q" +} + +module Raw { + private import semmle.code.cpp.ir.implementation.raw.IR + private import semmle.code.cpp.ir.implementation.unaliased_ssa.internal.SimpleSSA + + private MemoryLocation getAMemoryAccess(Instruction instr) { + result = getResultMemoryLocation(instr) or + result = getOperandMemoryLocation(instr.getAnOperand()) + } + + class RawPointsToTest extends InlineExpectationsTest { + RawPointsToTest() { this = "RawPointsToTest" } + + override string getARelevantTag() { result = "raw" } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + exists(Instruction instr, MemoryLocation memLocation | + memLocation = getAMemoryAccess(instr) and + tag = "raw" and + not ignoreAllocation(memLocation.getAllocation().getAllocationString()) and + value = memLocation.toString() and + element = instr.toString() and + location = instr.getLocation() + ) + } + } +} + +module UnaliasedSSA { + private import semmle.code.cpp.ir.implementation.unaliased_ssa.IR + private import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasedSSA + + private MemoryLocation getAMemoryAccess(Instruction instr) { + result = getResultMemoryLocation(instr) or + result = getOperandMemoryLocation(instr.getAnOperand()) + } + + class UnaliasedSSAPointsToTest extends InlineExpectationsTest { + UnaliasedSSAPointsToTest() { this = "UnaliasedSSAPointsToTest" } + + override string getARelevantTag() { result = "ussa" } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + exists(Instruction instr, MemoryLocation memLocation | + memLocation = getAMemoryAccess(instr) and + not memLocation instanceof AliasedVirtualVariable and + not memLocation instanceof AllNonLocalMemory and + tag = "ussa" and + not ignoreAllocation(memLocation.getAllocation().getAllocationString()) and + value = memLocation.toString() and + element = instr.toString() and + location = instr.getLocation() + ) + } + } +} From 7013bc6bf460cb5ba03622d46547dd0390029ef2 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 28 Jan 2020 10:57:07 -0700 Subject: [PATCH 072/148] C++: Update escape analysis tests to new API --- cpp/ql/test/library-tests/ir/escape/escape.ql | 4 ++-- .../test/library-tests/ir/escape/ssa_escape.ql | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cpp/ql/test/library-tests/ir/escape/escape.ql b/cpp/ql/test/library-tests/ir/escape/escape.ql index 109ff260b7d..9099fea159e 100644 --- a/cpp/ql/test/library-tests/ir/escape/escape.ql +++ b/cpp/ql/test/library-tests/ir/escape/escape.ql @@ -16,9 +16,9 @@ where exists(IRFunction irFunc | irFunc = var.getEnclosingIRFunction() and ( - shouldEscape(var) and variableAddressEscapes(var) + shouldEscape(var) and allocationEscapes(var) or - not shouldEscape(var) and not variableAddressEscapes(var) + not shouldEscape(var) and not allocationEscapes(var) ) ) select var diff --git a/cpp/ql/test/library-tests/ir/escape/ssa_escape.ql b/cpp/ql/test/library-tests/ir/escape/ssa_escape.ql index 8025a455fc9..e97cea7670d 100644 --- a/cpp/ql/test/library-tests/ir/escape/ssa_escape.ql +++ b/cpp/ql/test/library-tests/ir/escape/ssa_escape.ql @@ -1,23 +1,25 @@ import default import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasAnalysis +import semmle.code.cpp.ir.implementation.aliased_ssa.internal.AliasConfiguration import semmle.code.cpp.ir.implementation.unaliased_ssa.IR import semmle.code.cpp.ir.implementation.UseSoundEscapeAnalysis -predicate shouldEscape(IRAutomaticUserVariable var) { - exists(string name | - name = var.getVariable().getName() and - name.matches("no_%") - ) +class InterestingAllocation extends VariableAllocation { + IRUserVariable userVar; + + InterestingAllocation() { userVar = this.getIRVariable() } + + final predicate shouldEscape() { userVar.getVariable().getName().matches("no_%") } } -from IRAutomaticUserVariable var +from InterestingAllocation var where exists(IRFunction irFunc | irFunc = var.getEnclosingIRFunction() and ( - shouldEscape(var) and variableAddressEscapes(var) + var.shouldEscape() and allocationEscapes(var) or - not shouldEscape(var) and not variableAddressEscapes(var) + not var.shouldEscape() and not allocationEscapes(var) ) ) select var From dda32359fa6453704f727591762684aa7a92b05b Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 28 Jan 2020 10:58:05 -0700 Subject: [PATCH 073/148] C++: Accept IR dump test results changes due to new alias analysis --- .../ir/ssa/aliased_ssa_ir.expected | 207 +++++++++--------- .../ir/ssa/aliased_ssa_ir_unsound.expected | 207 +++++++++--------- .../syntax-zoo/aliased_ssa_sanity.expected | 4 +- .../GlobalValueNumbering/ir_gvn.expected | 120 +++++----- 4 files changed, 258 insertions(+), 280 deletions(-) diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected index 413384da8fe..15c631f727c 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected @@ -8,13 +8,12 @@ ssa.cpp: # 13| m13_5(Point *) = InitializeParameter[p] : &:r13_4 # 13| r13_6(Point *) = Load : &:r13_4, m13_5 # 13| m13_7(unknown) = InitializeIndirection[p] : &:r13_6 -# 13| m13_8(unknown) = Chi : total:m13_2, partial:m13_7 -# 13| r13_9(glval) = VariableAddress[which1] : -# 13| m13_10(bool) = InitializeParameter[which1] : &:r13_9 -# 13| r13_11(glval) = VariableAddress[which2] : -# 13| m13_12(bool) = InitializeParameter[which2] : &:r13_11 +# 13| r13_8(glval) = VariableAddress[which1] : +# 13| m13_9(bool) = InitializeParameter[which1] : &:r13_8 +# 13| r13_10(glval) = VariableAddress[which2] : +# 13| m13_11(bool) = InitializeParameter[which2] : &:r13_10 # 14| r14_1(glval) = VariableAddress[which1] : -# 14| r14_2(bool) = Load : &:r14_1, m13_10 +# 14| r14_2(bool) = Load : &:r14_1, m13_9 # 14| v14_3(void) = ConditionalBranch : r14_2 #-----| False -> Block 2 #-----| True -> Block 1 @@ -23,29 +22,31 @@ ssa.cpp: # 15| r15_1(glval) = VariableAddress[p] : # 15| r15_2(Point *) = Load : &:r15_1, m13_5 # 15| r15_3(glval) = FieldAddress[x] : r15_2 -# 15| r15_4(int) = Load : &:r15_3, ~m13_8 +# 15| r15_4(int) = Load : &:r15_3, ~m13_7 # 15| r15_5(int) = Constant[1] : # 15| r15_6(int) = Add : r15_4, r15_5 # 15| m15_7(int) = Store : &:r15_3, r15_6 -# 15| m15_8(unknown) = Chi : total:m13_8, partial:m15_7 +# 15| m15_8(unknown) = Chi : total:m13_7, partial:m15_7 #-----| Goto -> Block 3 # 18| Block 2 # 18| r18_1(glval) = VariableAddress[p] : # 18| r18_2(Point *) = Load : &:r18_1, m13_5 # 18| r18_3(glval) = FieldAddress[y] : r18_2 -# 18| r18_4(int) = Load : &:r18_3, ~m13_8 +# 18| r18_4(int) = Load : &:r18_3, ~m13_7 # 18| r18_5(int) = Constant[1] : # 18| r18_6(int) = Add : r18_4, r18_5 # 18| m18_7(int) = Store : &:r18_3, r18_6 -# 18| m18_8(unknown) = Chi : total:m13_8, partial:m18_7 +# 18| m18_8(unknown) = Chi : total:m13_7, partial:m18_7 #-----| Goto -> Block 3 # 21| Block 3 -# 21| m21_1(unknown) = Phi : from 1:~m15_8, from 2:~m18_8 -# 21| r21_2(glval) = VariableAddress[which2] : -# 21| r21_3(bool) = Load : &:r21_2, m13_12 -# 21| v21_4(void) = ConditionalBranch : r21_3 +# 21| m21_1(int) = Phi : from 1:~m13_7, from 2:m18_7 +# 21| m21_2(int) = Phi : from 1:m15_7, from 2:~m13_7 +# 21| m21_3(unknown) = Phi : from 1:m15_8, from 2:m18_8 +# 21| r21_4(glval) = VariableAddress[which2] : +# 21| r21_5(bool) = Load : &:r21_4, m13_11 +# 21| v21_6(void) = ConditionalBranch : r21_5 #-----| False -> Block 5 #-----| True -> Block 4 @@ -53,43 +54,45 @@ ssa.cpp: # 22| r22_1(glval) = VariableAddress[p] : # 22| r22_2(Point *) = Load : &:r22_1, m13_5 # 22| r22_3(glval) = FieldAddress[x] : r22_2 -# 22| r22_4(int) = Load : &:r22_3, ~m21_1 +# 22| r22_4(int) = Load : &:r22_3, m21_2 # 22| r22_5(int) = Constant[1] : # 22| r22_6(int) = Add : r22_4, r22_5 # 22| m22_7(int) = Store : &:r22_3, r22_6 -# 22| m22_8(unknown) = Chi : total:m21_1, partial:m22_7 +# 22| m22_8(unknown) = Chi : total:m21_3, partial:m22_7 #-----| Goto -> Block 6 # 25| Block 5 # 25| r25_1(glval) = VariableAddress[p] : # 25| r25_2(Point *) = Load : &:r25_1, m13_5 # 25| r25_3(glval) = FieldAddress[y] : r25_2 -# 25| r25_4(int) = Load : &:r25_3, ~m21_1 +# 25| r25_4(int) = Load : &:r25_3, m21_1 # 25| r25_5(int) = Constant[1] : # 25| r25_6(int) = Add : r25_4, r25_5 # 25| m25_7(int) = Store : &:r25_3, r25_6 -# 25| m25_8(unknown) = Chi : total:m21_1, partial:m25_7 +# 25| m25_8(unknown) = Chi : total:m21_3, partial:m25_7 #-----| Goto -> Block 6 # 28| Block 6 -# 28| m28_1(unknown) = Phi : from 4:~m22_8, from 5:~m25_8 -# 28| r28_2(glval) = VariableAddress[#return] : -# 28| r28_3(glval) = VariableAddress[p] : -# 28| r28_4(Point *) = Load : &:r28_3, m13_5 -# 28| r28_5(glval) = FieldAddress[x] : r28_4 -# 28| r28_6(int) = Load : &:r28_5, ~m28_1 -# 28| r28_7(glval) = VariableAddress[p] : -# 28| r28_8(Point *) = Load : &:r28_7, m13_5 -# 28| r28_9(glval) = FieldAddress[y] : r28_8 -# 28| r28_10(int) = Load : &:r28_9, ~m28_1 -# 28| r28_11(int) = Add : r28_6, r28_10 -# 28| m28_12(int) = Store : &:r28_2, r28_11 -# 13| v13_13(void) = ReturnIndirection : &:r13_6, ~m28_1 -# 13| r13_14(glval) = VariableAddress[#return] : -# 13| v13_15(void) = ReturnValue : &:r13_14, m28_12 -# 13| v13_16(void) = UnmodeledUse : mu* -# 13| v13_17(void) = AliasedUse : ~m28_1 -# 13| v13_18(void) = ExitFunction : +# 28| m28_1(int) = Phi : from 4:m21_1, from 5:m25_7 +# 28| m28_2(int) = Phi : from 4:m22_7, from 5:m21_2 +# 28| m28_3(unknown) = Phi : from 4:m22_8, from 5:m25_8 +# 28| r28_4(glval) = VariableAddress[#return] : +# 28| r28_5(glval) = VariableAddress[p] : +# 28| r28_6(Point *) = Load : &:r28_5, m13_5 +# 28| r28_7(glval) = FieldAddress[x] : r28_6 +# 28| r28_8(int) = Load : &:r28_7, m28_2 +# 28| r28_9(glval) = VariableAddress[p] : +# 28| r28_10(Point *) = Load : &:r28_9, m13_5 +# 28| r28_11(glval) = FieldAddress[y] : r28_10 +# 28| r28_12(int) = Load : &:r28_11, m28_1 +# 28| r28_13(int) = Add : r28_8, r28_12 +# 28| m28_14(int) = Store : &:r28_4, r28_13 +# 13| v13_12(void) = ReturnIndirection : &:r13_6, m28_3 +# 13| r13_13(glval) = VariableAddress[#return] : +# 13| v13_14(void) = ReturnValue : &:r13_13, m28_14 +# 13| v13_15(void) = UnmodeledUse : mu* +# 13| v13_16(void) = AliasedUse : ~m13_2 +# 13| v13_17(void) = ExitFunction : # 31| int UnreachableViaGoto() # 31| Block 0 @@ -212,7 +215,6 @@ ssa.cpp: # 68| m68_7(char *) = InitializeParameter[p] : &:r68_6 # 68| r68_8(char *) = Load : &:r68_6, m68_7 # 68| m68_9(unknown) = InitializeIndirection[p] : &:r68_8 -# 68| m68_10(unknown) = Chi : total:m68_2, partial:m68_9 #-----| Goto -> Block 3 # 70| Block 1 @@ -229,16 +231,16 @@ ssa.cpp: # 71| Block 2 # 71| v71_1(void) = NoOp : -# 68| v68_11(void) = ReturnIndirection : &:r68_8, ~m69_3 -# 68| v68_12(void) = ReturnVoid : -# 68| v68_13(void) = UnmodeledUse : mu* -# 68| v68_14(void) = AliasedUse : ~m69_3 -# 68| v68_15(void) = ExitFunction : +# 68| v68_10(void) = ReturnIndirection : &:r68_8, m68_9 +# 68| v68_11(void) = ReturnVoid : +# 68| v68_12(void) = UnmodeledUse : mu* +# 68| v68_13(void) = AliasedUse : ~m69_3 +# 68| v68_14(void) = ExitFunction : # 69| Block 3 # 69| m69_1(char *) = Phi : from 0:m68_7, from 1:m70_6 # 69| m69_2(int) = Phi : from 0:m68_5, from 1:m69_8 -# 69| m69_3(unknown) = Phi : from 0:~m68_10, from 1:~m70_9 +# 69| m69_3(unknown) = Phi : from 0:~m68_2, from 1:~m70_9 # 69| r69_4(glval) = VariableAddress[n] : # 69| r69_5(int) = Load : &:r69_4, m69_2 # 69| r69_6(int) = Constant[1] : @@ -759,20 +761,19 @@ ssa.cpp: # 179| m179_5(int *) = InitializeParameter[p] : &:r179_4 # 179| r179_6(int *) = Load : &:r179_4, m179_5 # 179| m179_7(unknown) = InitializeIndirection[p] : &:r179_6 -# 179| m179_8(unknown) = Chi : total:m179_2, partial:m179_7 -# 180| m180_1(unknown) = InlineAsm : ~m179_8 -# 180| m180_2(unknown) = Chi : total:m179_8, partial:m180_1 +# 180| m180_1(unknown) = InlineAsm : ~m179_2 +# 180| m180_2(unknown) = Chi : total:m179_2, partial:m180_1 # 181| r181_1(glval) = VariableAddress[#return] : # 181| r181_2(glval) = VariableAddress[p] : # 181| r181_3(int *) = Load : &:r181_2, m179_5 -# 181| r181_4(int) = Load : &:r181_3, ~m180_2 +# 181| r181_4(int) = Load : &:r181_3, ~m179_7 # 181| m181_5(int) = Store : &:r181_1, r181_4 -# 179| v179_9(void) = ReturnIndirection : &:r179_6, ~m180_2 -# 179| r179_10(glval) = VariableAddress[#return] : -# 179| v179_11(void) = ReturnValue : &:r179_10, m181_5 -# 179| v179_12(void) = UnmodeledUse : mu* -# 179| v179_13(void) = AliasedUse : ~m180_2 -# 179| v179_14(void) = ExitFunction : +# 179| v179_8(void) = ReturnIndirection : &:r179_6, m179_7 +# 179| r179_9(glval) = VariableAddress[#return] : +# 179| v179_10(void) = ReturnValue : &:r179_9, m181_5 +# 179| v179_11(void) = UnmodeledUse : mu* +# 179| v179_12(void) = AliasedUse : ~m180_2 +# 179| v179_13(void) = ExitFunction : # 184| void AsmStmtWithOutputs(unsigned int&, unsigned int&, unsigned int&, unsigned int&) # 184| Block 0 @@ -783,45 +784,41 @@ ssa.cpp: # 184| m184_5(unsigned int &) = InitializeParameter[a] : &:r184_4 # 184| r184_6(unsigned int &) = Load : &:r184_4, m184_5 # 184| m184_7(unknown) = InitializeIndirection[a] : &:r184_6 -# 184| m184_8(unknown) = Chi : total:m184_2, partial:m184_7 -# 184| r184_9(glval) = VariableAddress[b] : -# 184| m184_10(unsigned int &) = InitializeParameter[b] : &:r184_9 -# 184| r184_11(unsigned int &) = Load : &:r184_9, m184_10 -# 184| m184_12(unknown) = InitializeIndirection[b] : &:r184_11 -# 184| m184_13(unknown) = Chi : total:m184_8, partial:m184_12 -# 184| r184_14(glval) = VariableAddress[c] : -# 184| m184_15(unsigned int &) = InitializeParameter[c] : &:r184_14 -# 184| r184_16(unsigned int &) = Load : &:r184_14, m184_15 -# 184| m184_17(unknown) = InitializeIndirection[c] : &:r184_16 -# 184| m184_18(unknown) = Chi : total:m184_13, partial:m184_17 -# 184| r184_19(glval) = VariableAddress[d] : -# 184| m184_20(unsigned int &) = InitializeParameter[d] : &:r184_19 -# 184| r184_21(unsigned int &) = Load : &:r184_19, m184_20 -# 184| m184_22(unknown) = InitializeIndirection[d] : &:r184_21 -# 184| m184_23(unknown) = Chi : total:m184_18, partial:m184_22 +# 184| r184_8(glval) = VariableAddress[b] : +# 184| m184_9(unsigned int &) = InitializeParameter[b] : &:r184_8 +# 184| r184_10(unsigned int &) = Load : &:r184_8, m184_9 +# 184| m184_11(unknown) = InitializeIndirection[b] : &:r184_10 +# 184| r184_12(glval) = VariableAddress[c] : +# 184| m184_13(unsigned int &) = InitializeParameter[c] : &:r184_12 +# 184| r184_14(unsigned int &) = Load : &:r184_12, m184_13 +# 184| m184_15(unknown) = InitializeIndirection[c] : &:r184_14 +# 184| r184_16(glval) = VariableAddress[d] : +# 184| m184_17(unsigned int &) = InitializeParameter[d] : &:r184_16 +# 184| r184_18(unsigned int &) = Load : &:r184_16, m184_17 +# 184| m184_19(unknown) = InitializeIndirection[d] : &:r184_18 # 189| r189_1(glval) = VariableAddress[a] : # 189| r189_2(unsigned int &) = Load : &:r189_1, m184_5 # 189| r189_3(glval) = CopyValue : r189_2 # 189| r189_4(glval) = VariableAddress[b] : -# 189| r189_5(unsigned int &) = Load : &:r189_4, m184_10 +# 189| r189_5(unsigned int &) = Load : &:r189_4, m184_9 # 189| r189_6(glval) = CopyValue : r189_5 # 190| r190_1(glval) = VariableAddress[c] : -# 190| r190_2(unsigned int &) = Load : &:r190_1, m184_15 -# 190| r190_3(unsigned int) = Load : &:r190_2, ~m184_23 +# 190| r190_2(unsigned int &) = Load : &:r190_1, m184_13 +# 190| r190_3(unsigned int) = Load : &:r190_2, ~m184_15 # 190| r190_4(glval) = VariableAddress[d] : -# 190| r190_5(unsigned int &) = Load : &:r190_4, m184_20 -# 190| r190_6(unsigned int) = Load : &:r190_5, ~m184_23 -# 186| m186_1(unknown) = InlineAsm : ~m184_23, 0:r189_3, 1:r189_6, 2:r190_3, 3:r190_6 -# 186| m186_2(unknown) = Chi : total:m184_23, partial:m186_1 +# 190| r190_5(unsigned int &) = Load : &:r190_4, m184_17 +# 190| r190_6(unsigned int) = Load : &:r190_5, ~m184_19 +# 186| m186_1(unknown) = InlineAsm : ~m184_11, 0:r189_3, 1:r189_6, 2:r190_3, 3:r190_6 +# 186| m186_2(unknown) = Chi : total:m184_11, partial:m186_1 # 192| v192_1(void) = NoOp : -# 184| v184_24(void) = ReturnIndirection : &:r184_6, ~m186_2 -# 184| v184_25(void) = ReturnIndirection : &:r184_11, ~m186_2 -# 184| v184_26(void) = ReturnIndirection : &:r184_16, ~m186_2 -# 184| v184_27(void) = ReturnIndirection : &:r184_21, ~m186_2 -# 184| v184_28(void) = ReturnVoid : -# 184| v184_29(void) = UnmodeledUse : mu* -# 184| v184_30(void) = AliasedUse : ~m186_2 -# 184| v184_31(void) = ExitFunction : +# 184| v184_20(void) = ReturnIndirection : &:r184_6, ~m186_2 +# 184| v184_21(void) = ReturnIndirection : &:r184_10, ~m186_2 +# 184| v184_22(void) = ReturnIndirection : &:r184_14, m184_15 +# 184| v184_23(void) = ReturnIndirection : &:r184_18, m184_19 +# 184| v184_24(void) = ReturnVoid : +# 184| v184_25(void) = UnmodeledUse : mu* +# 184| v184_26(void) = AliasedUse : ~m186_2 +# 184| v184_27(void) = ExitFunction : # 198| int PureFunctions(char*, char*, int) # 198| Block 0 @@ -832,41 +829,39 @@ ssa.cpp: # 198| m198_5(char *) = InitializeParameter[str1] : &:r198_4 # 198| r198_6(char *) = Load : &:r198_4, m198_5 # 198| m198_7(unknown) = InitializeIndirection[str1] : &:r198_6 -# 198| m198_8(unknown) = Chi : total:m198_2, partial:m198_7 -# 198| r198_9(glval) = VariableAddress[str2] : -# 198| m198_10(char *) = InitializeParameter[str2] : &:r198_9 -# 198| r198_11(char *) = Load : &:r198_9, m198_10 -# 198| m198_12(unknown) = InitializeIndirection[str2] : &:r198_11 -# 198| m198_13(unknown) = Chi : total:m198_8, partial:m198_12 -# 198| r198_14(glval) = VariableAddress[x] : -# 198| m198_15(int) = InitializeParameter[x] : &:r198_14 +# 198| r198_8(glval) = VariableAddress[str2] : +# 198| m198_9(char *) = InitializeParameter[str2] : &:r198_8 +# 198| r198_10(char *) = Load : &:r198_8, m198_9 +# 198| m198_11(unknown) = InitializeIndirection[str2] : &:r198_10 +# 198| r198_12(glval) = VariableAddress[x] : +# 198| m198_13(int) = InitializeParameter[x] : &:r198_12 # 199| r199_1(glval) = VariableAddress[ret] : # 199| r199_2(glval) = FunctionAddress[strcmp] : # 199| r199_3(glval) = VariableAddress[str1] : # 199| r199_4(char *) = Load : &:r199_3, m198_5 # 199| r199_5(char *) = Convert : r199_4 # 199| r199_6(glval) = VariableAddress[str2] : -# 199| r199_7(char *) = Load : &:r199_6, m198_10 +# 199| r199_7(char *) = Load : &:r199_6, m198_9 # 199| r199_8(char *) = Convert : r199_7 # 199| r199_9(int) = Call : func:r199_2, 0:r199_5, 1:r199_8 -# 199| v199_10(void) = ^CallReadSideEffect : ~m198_13 -# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_13 -# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_13 +# 199| v199_10(void) = ^CallReadSideEffect : ~m198_2 +# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_7 +# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_11 # 199| m199_13(int) = Store : &:r199_1, r199_9 # 200| r200_1(glval) = FunctionAddress[strlen] : # 200| r200_2(glval) = VariableAddress[str1] : # 200| r200_3(char *) = Load : &:r200_2, m198_5 # 200| r200_4(char *) = Convert : r200_3 # 200| r200_5(int) = Call : func:r200_1, 0:r200_4 -# 200| v200_6(void) = ^CallReadSideEffect : ~m198_13 -# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_13 +# 200| v200_6(void) = ^CallReadSideEffect : ~m198_2 +# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_7 # 200| r200_8(glval) = VariableAddress[ret] : # 200| r200_9(int) = Load : &:r200_8, m199_13 # 200| r200_10(int) = Add : r200_9, r200_5 # 200| m200_11(int) = Store : &:r200_8, r200_10 # 201| r201_1(glval) = FunctionAddress[abs] : # 201| r201_2(glval) = VariableAddress[x] : -# 201| r201_3(int) = Load : &:r201_2, m198_15 +# 201| r201_3(int) = Load : &:r201_2, m198_13 # 201| r201_4(int) = Call : func:r201_1, 0:r201_3 # 201| r201_5(glval) = VariableAddress[ret] : # 201| r201_6(int) = Load : &:r201_5, m200_11 @@ -876,13 +871,13 @@ ssa.cpp: # 202| r202_2(glval) = VariableAddress[ret] : # 202| r202_3(int) = Load : &:r202_2, m201_8 # 202| m202_4(int) = Store : &:r202_1, r202_3 -# 198| v198_16(void) = ReturnIndirection : &:r198_6, ~m198_13 -# 198| v198_17(void) = ReturnIndirection : &:r198_11, ~m198_13 -# 198| r198_18(glval) = VariableAddress[#return] : -# 198| v198_19(void) = ReturnValue : &:r198_18, m202_4 -# 198| v198_20(void) = UnmodeledUse : mu* -# 198| v198_21(void) = AliasedUse : ~m198_13 -# 198| v198_22(void) = ExitFunction : +# 198| v198_14(void) = ReturnIndirection : &:r198_6, m198_7 +# 198| v198_15(void) = ReturnIndirection : &:r198_10, m198_11 +# 198| r198_16(glval) = VariableAddress[#return] : +# 198| v198_17(void) = ReturnValue : &:r198_16, m202_4 +# 198| v198_18(void) = UnmodeledUse : mu* +# 198| v198_19(void) = AliasedUse : ~m198_2 +# 198| v198_20(void) = ExitFunction : # 207| int ModeledCallTarget(int) # 207| Block 0 diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected index b1a174f2b0b..ef8570bd1a5 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected @@ -8,13 +8,12 @@ ssa.cpp: # 13| m13_5(Point *) = InitializeParameter[p] : &:r13_4 # 13| r13_6(Point *) = Load : &:r13_4, m13_5 # 13| m13_7(unknown) = InitializeIndirection[p] : &:r13_6 -# 13| m13_8(unknown) = Chi : total:m13_2, partial:m13_7 -# 13| r13_9(glval) = VariableAddress[which1] : -# 13| m13_10(bool) = InitializeParameter[which1] : &:r13_9 -# 13| r13_11(glval) = VariableAddress[which2] : -# 13| m13_12(bool) = InitializeParameter[which2] : &:r13_11 +# 13| r13_8(glval) = VariableAddress[which1] : +# 13| m13_9(bool) = InitializeParameter[which1] : &:r13_8 +# 13| r13_10(glval) = VariableAddress[which2] : +# 13| m13_11(bool) = InitializeParameter[which2] : &:r13_10 # 14| r14_1(glval) = VariableAddress[which1] : -# 14| r14_2(bool) = Load : &:r14_1, m13_10 +# 14| r14_2(bool) = Load : &:r14_1, m13_9 # 14| v14_3(void) = ConditionalBranch : r14_2 #-----| False -> Block 2 #-----| True -> Block 1 @@ -23,29 +22,31 @@ ssa.cpp: # 15| r15_1(glval) = VariableAddress[p] : # 15| r15_2(Point *) = Load : &:r15_1, m13_5 # 15| r15_3(glval) = FieldAddress[x] : r15_2 -# 15| r15_4(int) = Load : &:r15_3, ~m13_8 +# 15| r15_4(int) = Load : &:r15_3, ~m13_7 # 15| r15_5(int) = Constant[1] : # 15| r15_6(int) = Add : r15_4, r15_5 # 15| m15_7(int) = Store : &:r15_3, r15_6 -# 15| m15_8(unknown) = Chi : total:m13_8, partial:m15_7 +# 15| m15_8(unknown) = Chi : total:m13_7, partial:m15_7 #-----| Goto -> Block 3 # 18| Block 2 # 18| r18_1(glval) = VariableAddress[p] : # 18| r18_2(Point *) = Load : &:r18_1, m13_5 # 18| r18_3(glval) = FieldAddress[y] : r18_2 -# 18| r18_4(int) = Load : &:r18_3, ~m13_8 +# 18| r18_4(int) = Load : &:r18_3, ~m13_7 # 18| r18_5(int) = Constant[1] : # 18| r18_6(int) = Add : r18_4, r18_5 # 18| m18_7(int) = Store : &:r18_3, r18_6 -# 18| m18_8(unknown) = Chi : total:m13_8, partial:m18_7 +# 18| m18_8(unknown) = Chi : total:m13_7, partial:m18_7 #-----| Goto -> Block 3 # 21| Block 3 -# 21| m21_1(unknown) = Phi : from 1:~m15_8, from 2:~m18_8 -# 21| r21_2(glval) = VariableAddress[which2] : -# 21| r21_3(bool) = Load : &:r21_2, m13_12 -# 21| v21_4(void) = ConditionalBranch : r21_3 +# 21| m21_1(int) = Phi : from 1:~m13_7, from 2:m18_7 +# 21| m21_2(int) = Phi : from 1:m15_7, from 2:~m13_7 +# 21| m21_3(unknown) = Phi : from 1:m15_8, from 2:m18_8 +# 21| r21_4(glval) = VariableAddress[which2] : +# 21| r21_5(bool) = Load : &:r21_4, m13_11 +# 21| v21_6(void) = ConditionalBranch : r21_5 #-----| False -> Block 5 #-----| True -> Block 4 @@ -53,43 +54,45 @@ ssa.cpp: # 22| r22_1(glval) = VariableAddress[p] : # 22| r22_2(Point *) = Load : &:r22_1, m13_5 # 22| r22_3(glval) = FieldAddress[x] : r22_2 -# 22| r22_4(int) = Load : &:r22_3, ~m21_1 +# 22| r22_4(int) = Load : &:r22_3, m21_2 # 22| r22_5(int) = Constant[1] : # 22| r22_6(int) = Add : r22_4, r22_5 # 22| m22_7(int) = Store : &:r22_3, r22_6 -# 22| m22_8(unknown) = Chi : total:m21_1, partial:m22_7 +# 22| m22_8(unknown) = Chi : total:m21_3, partial:m22_7 #-----| Goto -> Block 6 # 25| Block 5 # 25| r25_1(glval) = VariableAddress[p] : # 25| r25_2(Point *) = Load : &:r25_1, m13_5 # 25| r25_3(glval) = FieldAddress[y] : r25_2 -# 25| r25_4(int) = Load : &:r25_3, ~m21_1 +# 25| r25_4(int) = Load : &:r25_3, m21_1 # 25| r25_5(int) = Constant[1] : # 25| r25_6(int) = Add : r25_4, r25_5 # 25| m25_7(int) = Store : &:r25_3, r25_6 -# 25| m25_8(unknown) = Chi : total:m21_1, partial:m25_7 +# 25| m25_8(unknown) = Chi : total:m21_3, partial:m25_7 #-----| Goto -> Block 6 # 28| Block 6 -# 28| m28_1(unknown) = Phi : from 4:~m22_8, from 5:~m25_8 -# 28| r28_2(glval) = VariableAddress[#return] : -# 28| r28_3(glval) = VariableAddress[p] : -# 28| r28_4(Point *) = Load : &:r28_3, m13_5 -# 28| r28_5(glval) = FieldAddress[x] : r28_4 -# 28| r28_6(int) = Load : &:r28_5, ~m28_1 -# 28| r28_7(glval) = VariableAddress[p] : -# 28| r28_8(Point *) = Load : &:r28_7, m13_5 -# 28| r28_9(glval) = FieldAddress[y] : r28_8 -# 28| r28_10(int) = Load : &:r28_9, ~m28_1 -# 28| r28_11(int) = Add : r28_6, r28_10 -# 28| m28_12(int) = Store : &:r28_2, r28_11 -# 13| v13_13(void) = ReturnIndirection : &:r13_6, ~m28_1 -# 13| r13_14(glval) = VariableAddress[#return] : -# 13| v13_15(void) = ReturnValue : &:r13_14, m28_12 -# 13| v13_16(void) = UnmodeledUse : mu* -# 13| v13_17(void) = AliasedUse : ~m28_1 -# 13| v13_18(void) = ExitFunction : +# 28| m28_1(int) = Phi : from 4:m21_1, from 5:m25_7 +# 28| m28_2(int) = Phi : from 4:m22_7, from 5:m21_2 +# 28| m28_3(unknown) = Phi : from 4:m22_8, from 5:m25_8 +# 28| r28_4(glval) = VariableAddress[#return] : +# 28| r28_5(glval) = VariableAddress[p] : +# 28| r28_6(Point *) = Load : &:r28_5, m13_5 +# 28| r28_7(glval) = FieldAddress[x] : r28_6 +# 28| r28_8(int) = Load : &:r28_7, m28_2 +# 28| r28_9(glval) = VariableAddress[p] : +# 28| r28_10(Point *) = Load : &:r28_9, m13_5 +# 28| r28_11(glval) = FieldAddress[y] : r28_10 +# 28| r28_12(int) = Load : &:r28_11, m28_1 +# 28| r28_13(int) = Add : r28_8, r28_12 +# 28| m28_14(int) = Store : &:r28_4, r28_13 +# 13| v13_12(void) = ReturnIndirection : &:r13_6, m28_3 +# 13| r13_13(glval) = VariableAddress[#return] : +# 13| v13_14(void) = ReturnValue : &:r13_13, m28_14 +# 13| v13_15(void) = UnmodeledUse : mu* +# 13| v13_16(void) = AliasedUse : ~m13_2 +# 13| v13_17(void) = ExitFunction : # 31| int UnreachableViaGoto() # 31| Block 0 @@ -212,7 +215,6 @@ ssa.cpp: # 68| m68_7(char *) = InitializeParameter[p] : &:r68_6 # 68| r68_8(char *) = Load : &:r68_6, m68_7 # 68| m68_9(unknown) = InitializeIndirection[p] : &:r68_8 -# 68| m68_10(unknown) = Chi : total:m68_2, partial:m68_9 #-----| Goto -> Block 3 # 70| Block 1 @@ -229,16 +231,16 @@ ssa.cpp: # 71| Block 2 # 71| v71_1(void) = NoOp : -# 68| v68_11(void) = ReturnIndirection : &:r68_8, ~m69_3 -# 68| v68_12(void) = ReturnVoid : -# 68| v68_13(void) = UnmodeledUse : mu* -# 68| v68_14(void) = AliasedUse : ~m69_3 -# 68| v68_15(void) = ExitFunction : +# 68| v68_10(void) = ReturnIndirection : &:r68_8, m68_9 +# 68| v68_11(void) = ReturnVoid : +# 68| v68_12(void) = UnmodeledUse : mu* +# 68| v68_13(void) = AliasedUse : ~m69_3 +# 68| v68_14(void) = ExitFunction : # 69| Block 3 # 69| m69_1(char *) = Phi : from 0:m68_7, from 1:m70_6 # 69| m69_2(int) = Phi : from 0:m68_5, from 1:m69_8 -# 69| m69_3(unknown) = Phi : from 0:~m68_10, from 1:~m70_9 +# 69| m69_3(unknown) = Phi : from 0:~m68_2, from 1:~m70_9 # 69| r69_4(glval) = VariableAddress[n] : # 69| r69_5(int) = Load : &:r69_4, m69_2 # 69| r69_6(int) = Constant[1] : @@ -756,20 +758,19 @@ ssa.cpp: # 179| m179_5(int *) = InitializeParameter[p] : &:r179_4 # 179| r179_6(int *) = Load : &:r179_4, m179_5 # 179| m179_7(unknown) = InitializeIndirection[p] : &:r179_6 -# 179| m179_8(unknown) = Chi : total:m179_2, partial:m179_7 -# 180| m180_1(unknown) = InlineAsm : ~m179_8 -# 180| m180_2(unknown) = Chi : total:m179_8, partial:m180_1 +# 180| m180_1(unknown) = InlineAsm : ~m179_2 +# 180| m180_2(unknown) = Chi : total:m179_2, partial:m180_1 # 181| r181_1(glval) = VariableAddress[#return] : # 181| r181_2(glval) = VariableAddress[p] : # 181| r181_3(int *) = Load : &:r181_2, m179_5 -# 181| r181_4(int) = Load : &:r181_3, ~m180_2 +# 181| r181_4(int) = Load : &:r181_3, ~m179_7 # 181| m181_5(int) = Store : &:r181_1, r181_4 -# 179| v179_9(void) = ReturnIndirection : &:r179_6, ~m180_2 -# 179| r179_10(glval) = VariableAddress[#return] : -# 179| v179_11(void) = ReturnValue : &:r179_10, m181_5 -# 179| v179_12(void) = UnmodeledUse : mu* -# 179| v179_13(void) = AliasedUse : ~m180_2 -# 179| v179_14(void) = ExitFunction : +# 179| v179_8(void) = ReturnIndirection : &:r179_6, m179_7 +# 179| r179_9(glval) = VariableAddress[#return] : +# 179| v179_10(void) = ReturnValue : &:r179_9, m181_5 +# 179| v179_11(void) = UnmodeledUse : mu* +# 179| v179_12(void) = AliasedUse : ~m180_2 +# 179| v179_13(void) = ExitFunction : # 184| void AsmStmtWithOutputs(unsigned int&, unsigned int&, unsigned int&, unsigned int&) # 184| Block 0 @@ -780,45 +781,41 @@ ssa.cpp: # 184| m184_5(unsigned int &) = InitializeParameter[a] : &:r184_4 # 184| r184_6(unsigned int &) = Load : &:r184_4, m184_5 # 184| m184_7(unknown) = InitializeIndirection[a] : &:r184_6 -# 184| m184_8(unknown) = Chi : total:m184_2, partial:m184_7 -# 184| r184_9(glval) = VariableAddress[b] : -# 184| m184_10(unsigned int &) = InitializeParameter[b] : &:r184_9 -# 184| r184_11(unsigned int &) = Load : &:r184_9, m184_10 -# 184| m184_12(unknown) = InitializeIndirection[b] : &:r184_11 -# 184| m184_13(unknown) = Chi : total:m184_8, partial:m184_12 -# 184| r184_14(glval) = VariableAddress[c] : -# 184| m184_15(unsigned int &) = InitializeParameter[c] : &:r184_14 -# 184| r184_16(unsigned int &) = Load : &:r184_14, m184_15 -# 184| m184_17(unknown) = InitializeIndirection[c] : &:r184_16 -# 184| m184_18(unknown) = Chi : total:m184_13, partial:m184_17 -# 184| r184_19(glval) = VariableAddress[d] : -# 184| m184_20(unsigned int &) = InitializeParameter[d] : &:r184_19 -# 184| r184_21(unsigned int &) = Load : &:r184_19, m184_20 -# 184| m184_22(unknown) = InitializeIndirection[d] : &:r184_21 -# 184| m184_23(unknown) = Chi : total:m184_18, partial:m184_22 +# 184| r184_8(glval) = VariableAddress[b] : +# 184| m184_9(unsigned int &) = InitializeParameter[b] : &:r184_8 +# 184| r184_10(unsigned int &) = Load : &:r184_8, m184_9 +# 184| m184_11(unknown) = InitializeIndirection[b] : &:r184_10 +# 184| r184_12(glval) = VariableAddress[c] : +# 184| m184_13(unsigned int &) = InitializeParameter[c] : &:r184_12 +# 184| r184_14(unsigned int &) = Load : &:r184_12, m184_13 +# 184| m184_15(unknown) = InitializeIndirection[c] : &:r184_14 +# 184| r184_16(glval) = VariableAddress[d] : +# 184| m184_17(unsigned int &) = InitializeParameter[d] : &:r184_16 +# 184| r184_18(unsigned int &) = Load : &:r184_16, m184_17 +# 184| m184_19(unknown) = InitializeIndirection[d] : &:r184_18 # 189| r189_1(glval) = VariableAddress[a] : # 189| r189_2(unsigned int &) = Load : &:r189_1, m184_5 # 189| r189_3(glval) = CopyValue : r189_2 # 189| r189_4(glval) = VariableAddress[b] : -# 189| r189_5(unsigned int &) = Load : &:r189_4, m184_10 +# 189| r189_5(unsigned int &) = Load : &:r189_4, m184_9 # 189| r189_6(glval) = CopyValue : r189_5 # 190| r190_1(glval) = VariableAddress[c] : -# 190| r190_2(unsigned int &) = Load : &:r190_1, m184_15 -# 190| r190_3(unsigned int) = Load : &:r190_2, ~m184_23 +# 190| r190_2(unsigned int &) = Load : &:r190_1, m184_13 +# 190| r190_3(unsigned int) = Load : &:r190_2, ~m184_15 # 190| r190_4(glval) = VariableAddress[d] : -# 190| r190_5(unsigned int &) = Load : &:r190_4, m184_20 -# 190| r190_6(unsigned int) = Load : &:r190_5, ~m184_23 -# 186| m186_1(unknown) = InlineAsm : ~m184_23, 0:r189_3, 1:r189_6, 2:r190_3, 3:r190_6 -# 186| m186_2(unknown) = Chi : total:m184_23, partial:m186_1 +# 190| r190_5(unsigned int &) = Load : &:r190_4, m184_17 +# 190| r190_6(unsigned int) = Load : &:r190_5, ~m184_19 +# 186| m186_1(unknown) = InlineAsm : ~m184_2, 0:r189_3, 1:r189_6, 2:r190_3, 3:r190_6 +# 186| m186_2(unknown) = Chi : total:m184_2, partial:m186_1 # 192| v192_1(void) = NoOp : -# 184| v184_24(void) = ReturnIndirection : &:r184_6, ~m186_2 -# 184| v184_25(void) = ReturnIndirection : &:r184_11, ~m186_2 -# 184| v184_26(void) = ReturnIndirection : &:r184_16, ~m186_2 -# 184| v184_27(void) = ReturnIndirection : &:r184_21, ~m186_2 -# 184| v184_28(void) = ReturnVoid : -# 184| v184_29(void) = UnmodeledUse : mu* -# 184| v184_30(void) = AliasedUse : ~m186_2 -# 184| v184_31(void) = ExitFunction : +# 184| v184_20(void) = ReturnIndirection : &:r184_6, m184_7 +# 184| v184_21(void) = ReturnIndirection : &:r184_10, m184_11 +# 184| v184_22(void) = ReturnIndirection : &:r184_14, m184_15 +# 184| v184_23(void) = ReturnIndirection : &:r184_18, m184_19 +# 184| v184_24(void) = ReturnVoid : +# 184| v184_25(void) = UnmodeledUse : mu* +# 184| v184_26(void) = AliasedUse : ~m186_2 +# 184| v184_27(void) = ExitFunction : # 198| int PureFunctions(char*, char*, int) # 198| Block 0 @@ -829,41 +826,39 @@ ssa.cpp: # 198| m198_5(char *) = InitializeParameter[str1] : &:r198_4 # 198| r198_6(char *) = Load : &:r198_4, m198_5 # 198| m198_7(unknown) = InitializeIndirection[str1] : &:r198_6 -# 198| m198_8(unknown) = Chi : total:m198_2, partial:m198_7 -# 198| r198_9(glval) = VariableAddress[str2] : -# 198| m198_10(char *) = InitializeParameter[str2] : &:r198_9 -# 198| r198_11(char *) = Load : &:r198_9, m198_10 -# 198| m198_12(unknown) = InitializeIndirection[str2] : &:r198_11 -# 198| m198_13(unknown) = Chi : total:m198_8, partial:m198_12 -# 198| r198_14(glval) = VariableAddress[x] : -# 198| m198_15(int) = InitializeParameter[x] : &:r198_14 +# 198| r198_8(glval) = VariableAddress[str2] : +# 198| m198_9(char *) = InitializeParameter[str2] : &:r198_8 +# 198| r198_10(char *) = Load : &:r198_8, m198_9 +# 198| m198_11(unknown) = InitializeIndirection[str2] : &:r198_10 +# 198| r198_12(glval) = VariableAddress[x] : +# 198| m198_13(int) = InitializeParameter[x] : &:r198_12 # 199| r199_1(glval) = VariableAddress[ret] : # 199| r199_2(glval) = FunctionAddress[strcmp] : # 199| r199_3(glval) = VariableAddress[str1] : # 199| r199_4(char *) = Load : &:r199_3, m198_5 # 199| r199_5(char *) = Convert : r199_4 # 199| r199_6(glval) = VariableAddress[str2] : -# 199| r199_7(char *) = Load : &:r199_6, m198_10 +# 199| r199_7(char *) = Load : &:r199_6, m198_9 # 199| r199_8(char *) = Convert : r199_7 # 199| r199_9(int) = Call : func:r199_2, 0:r199_5, 1:r199_8 -# 199| v199_10(void) = ^CallReadSideEffect : ~m198_13 -# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_13 -# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_13 +# 199| v199_10(void) = ^CallReadSideEffect : ~m198_2 +# 199| v199_11(void) = ^BufferReadSideEffect[0] : &:r199_5, ~m198_7 +# 199| v199_12(void) = ^BufferReadSideEffect[1] : &:r199_8, ~m198_11 # 199| m199_13(int) = Store : &:r199_1, r199_9 # 200| r200_1(glval) = FunctionAddress[strlen] : # 200| r200_2(glval) = VariableAddress[str1] : # 200| r200_3(char *) = Load : &:r200_2, m198_5 # 200| r200_4(char *) = Convert : r200_3 # 200| r200_5(int) = Call : func:r200_1, 0:r200_4 -# 200| v200_6(void) = ^CallReadSideEffect : ~m198_13 -# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_13 +# 200| v200_6(void) = ^CallReadSideEffect : ~m198_2 +# 200| v200_7(void) = ^BufferReadSideEffect[0] : &:r200_4, ~m198_7 # 200| r200_8(glval) = VariableAddress[ret] : # 200| r200_9(int) = Load : &:r200_8, m199_13 # 200| r200_10(int) = Add : r200_9, r200_5 # 200| m200_11(int) = Store : &:r200_8, r200_10 # 201| r201_1(glval) = FunctionAddress[abs] : # 201| r201_2(glval) = VariableAddress[x] : -# 201| r201_3(int) = Load : &:r201_2, m198_15 +# 201| r201_3(int) = Load : &:r201_2, m198_13 # 201| r201_4(int) = Call : func:r201_1, 0:r201_3 # 201| r201_5(glval) = VariableAddress[ret] : # 201| r201_6(int) = Load : &:r201_5, m200_11 @@ -873,13 +868,13 @@ ssa.cpp: # 202| r202_2(glval) = VariableAddress[ret] : # 202| r202_3(int) = Load : &:r202_2, m201_8 # 202| m202_4(int) = Store : &:r202_1, r202_3 -# 198| v198_16(void) = ReturnIndirection : &:r198_6, ~m198_13 -# 198| v198_17(void) = ReturnIndirection : &:r198_11, ~m198_13 -# 198| r198_18(glval) = VariableAddress[#return] : -# 198| v198_19(void) = ReturnValue : &:r198_18, m202_4 -# 198| v198_20(void) = UnmodeledUse : mu* -# 198| v198_21(void) = AliasedUse : ~m198_13 -# 198| v198_22(void) = ExitFunction : +# 198| v198_14(void) = ReturnIndirection : &:r198_6, m198_7 +# 198| v198_15(void) = ReturnIndirection : &:r198_10, m198_11 +# 198| r198_16(glval) = VariableAddress[#return] : +# 198| v198_17(void) = ReturnValue : &:r198_16, m202_4 +# 198| v198_18(void) = UnmodeledUse : mu* +# 198| v198_19(void) = AliasedUse : ~m198_2 +# 198| v198_20(void) = ExitFunction : # 207| int ModeledCallTarget(int) # 207| Block 0 diff --git a/cpp/ql/test/library-tests/syntax-zoo/aliased_ssa_sanity.expected b/cpp/ql/test/library-tests/syntax-zoo/aliased_ssa_sanity.expected index ebd22e357c2..c0671e967cc 100644 --- a/cpp/ql/test/library-tests/syntax-zoo/aliased_ssa_sanity.expected +++ b/cpp/ql/test/library-tests/syntax-zoo/aliased_ssa_sanity.expected @@ -35,14 +35,14 @@ missingOperandType duplicateChiOperand sideEffectWithoutPrimary instructionWithoutSuccessor -| VacuousDestructorCall.cpp:2:29:2:29 | Chi: y | +| VacuousDestructorCall.cpp:2:29:2:29 | InitializeIndirection: y | | condition_decls.cpp:16:19:16:20 | Chi: call to BoxedInt | | condition_decls.cpp:26:23:26:24 | Chi: call to BoxedInt | | condition_decls.cpp:41:22:41:23 | Chi: call to BoxedInt | | condition_decls.cpp:48:52:48:53 | Chi: call to BoxedInt | | cpp17.cpp:15:11:15:21 | Convert: (void *)... | | misc.c:171:10:171:13 | Uninitialized: definition of str2 | -| misc.c:219:47:219:48 | Chi: sp | +| misc.c:219:47:219:48 | InitializeIndirection: sp | | ms_try_except.cpp:3:9:3:9 | Uninitialized: definition of x | | ms_try_mix.cpp:11:12:11:15 | Chi: call to C | | ms_try_mix.cpp:28:12:28:15 | Chi: call to C | diff --git a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected index 38386b9c2fd..d8cda203eff 100644 --- a/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected +++ b/cpp/ql/test/library-tests/valuenumbering/GlobalValueNumbering/ir_gvn.expected @@ -266,8 +266,6 @@ test.cpp: # 39| valnum = m39_9 # 39| m39_11(unknown) = InitializeIndirection[p2] : &:r39_10 # 39| valnum = unique -# 39| m39_12(unknown) = Chi : total:m39_2, partial:m39_11 -# 39| valnum = unique # 40| r40_1(glval) = VariableAddress[x] : # 40| valnum = r40_1 # 40| m40_2(int) = Uninitialized[x] : &:r40_1 @@ -292,7 +290,7 @@ test.cpp: # 43| valnum = r43_5 # 43| r43_6(glval) = VariableAddress[global03] : # 43| valnum = r43_6 -# 43| r43_7(int) = Load : &:r43_6, ~m39_12 +# 43| r43_7(int) = Load : &:r43_6, ~m39_2 # 43| valnum = unique # 43| r43_8(int) = Add : r43_5, r43_7 # 43| valnum = r43_8 @@ -310,7 +308,7 @@ test.cpp: # 44| valnum = m39_9 # 44| m44_5(int) = Store : &:r44_4, r44_1 # 44| valnum = r44_1 -# 44| m44_6(unknown) = Chi : total:m39_12, partial:m44_5 +# 44| m44_6(unknown) = Chi : total:m39_11, partial:m44_5 # 44| valnum = unique # 45| r45_1(glval) = VariableAddress[p0] : # 45| valnum = r39_4 @@ -324,7 +322,7 @@ test.cpp: # 45| valnum = r43_5 # 45| r45_6(glval) = VariableAddress[global03] : # 45| valnum = r43_6 -# 45| r45_7(int) = Load : &:r45_6, ~m44_6 +# 45| r45_7(int) = Load : &:r45_6, ~m39_2 # 45| valnum = unique # 45| r45_8(int) = Add : r45_5, r45_7 # 45| valnum = r45_8 @@ -341,13 +339,13 @@ test.cpp: # 46| m46_4(int) = Store : &:r46_3, r46_2 # 46| valnum = r45_8 # 47| v47_1(void) = NoOp : -# 39| v39_13(void) = ReturnIndirection : &:r39_10, ~m44_6 -# 39| r39_14(glval) = VariableAddress[#return] : +# 39| v39_12(void) = ReturnIndirection : &:r39_10, ~m44_6 +# 39| r39_13(glval) = VariableAddress[#return] : # 39| valnum = unique -# 39| v39_15(void) = ReturnValue : &:r39_14 -# 39| v39_16(void) = UnmodeledUse : mu* -# 39| v39_17(void) = AliasedUse : ~m44_6 -# 39| v39_18(void) = ExitFunction : +# 39| v39_14(void) = ReturnValue : &:r39_13 +# 39| v39_15(void) = UnmodeledUse : mu* +# 39| v39_16(void) = AliasedUse : ~m39_2 +# 39| v39_17(void) = ExitFunction : # 49| unsigned int my_strspn(char const*, char const*) # 49| Block 0 @@ -364,17 +362,13 @@ test.cpp: # 49| valnum = m49_5 # 49| m49_7(unknown) = InitializeIndirection[str] : &:r49_6 # 49| valnum = unique -# 49| m49_8(unknown) = Chi : total:m49_2, partial:m49_7 -# 49| valnum = unique -# 49| r49_9(glval) = VariableAddress[chars] : -# 49| valnum = r49_9 -# 49| m49_10(char *) = InitializeParameter[chars] : &:r49_9 -# 49| valnum = m49_10 -# 49| r49_11(char *) = Load : &:r49_9, m49_10 -# 49| valnum = m49_10 -# 49| m49_12(unknown) = InitializeIndirection[chars] : &:r49_11 -# 49| valnum = unique -# 49| m49_13(unknown) = Chi : total:m49_8, partial:m49_12 +# 49| r49_8(glval) = VariableAddress[chars] : +# 49| valnum = r49_8 +# 49| m49_9(char *) = InitializeParameter[chars] : &:r49_8 +# 49| valnum = m49_9 +# 49| r49_10(char *) = Load : &:r49_8, m49_9 +# 49| valnum = m49_9 +# 49| m49_11(unknown) = InitializeIndirection[chars] : &:r49_10 # 49| valnum = unique # 50| r50_1(glval) = VariableAddress[ptr] : # 50| valnum = r50_1 @@ -395,7 +389,7 @@ test.cpp: # 53| valnum = r49_4 # 53| r53_3(char *) = Load : &:r53_2, m49_5 # 53| valnum = m49_5 -# 53| r53_4(char) = Load : &:r53_3, ~m49_13 +# 53| r53_4(char) = Load : &:r53_3, ~m49_7 # 53| valnum = unique # 53| r53_5(int) = Convert : r53_4 # 53| valnum = unique @@ -409,13 +403,13 @@ test.cpp: # 55| Block 2 # 55| r55_1(glval) = VariableAddress[chars] : -# 55| valnum = r49_9 -# 55| r55_2(char *) = Load : &:r55_1, m49_10 -# 55| valnum = m49_10 +# 55| valnum = r49_8 +# 55| r55_2(char *) = Load : &:r55_1, m49_9 +# 55| valnum = m49_9 # 55| r55_3(glval) = VariableAddress[ptr] : # 55| valnum = r50_1 # 55| m55_4(char *) = Store : &:r55_3, r55_2 -# 55| valnum = m49_10 +# 55| valnum = m49_9 #-----| Goto -> Block 3 # 56| Block 3 @@ -425,7 +419,7 @@ test.cpp: # 56| valnum = r50_1 # 56| r56_3(char *) = Load : &:r56_2, m56_1 # 56| valnum = m56_1 -# 56| r56_4(char) = Load : &:r56_3, ~m49_13 +# 56| r56_4(char) = Load : &:r56_3, ~m49_2 # 56| valnum = unique # 56| r56_5(int) = Convert : r56_4 # 56| valnum = unique @@ -433,7 +427,7 @@ test.cpp: # 56| valnum = r49_4 # 56| r56_7(char *) = Load : &:r56_6, m49_5 # 56| valnum = m49_5 -# 56| r56_8(char) = Load : &:r56_7, ~m49_13 +# 56| r56_8(char) = Load : &:r56_7, ~m49_7 # 56| valnum = unique # 56| r56_9(int) = Convert : r56_8 # 56| valnum = unique @@ -448,7 +442,7 @@ test.cpp: # 56| valnum = r50_1 # 56| r56_13(char *) = Load : &:r56_12, m56_1 # 56| valnum = m56_1 -# 56| r56_14(char) = Load : &:r56_13, ~m49_13 +# 56| r56_14(char) = Load : &:r56_13, ~m49_2 # 56| valnum = unique # 56| r56_15(int) = Convert : r56_14 # 56| valnum = unique @@ -478,7 +472,7 @@ test.cpp: # 59| valnum = r50_1 # 59| r59_2(char *) = Load : &:r59_1, m56_1 # 59| valnum = m56_1 -# 59| r59_3(char) = Load : &:r59_2, ~m49_13 +# 59| r59_3(char) = Load : &:r59_2, ~m49_2 # 59| valnum = unique # 59| r59_4(int) = Convert : r59_3 # 59| valnum = unique @@ -517,14 +511,14 @@ test.cpp: # 65| valnum = m53_1 # 65| m65_4(unsigned int) = Store : &:r65_1, r65_3 # 65| valnum = m53_1 -# 49| v49_14(void) = ReturnIndirection : &:r49_6, ~m49_13 -# 49| v49_15(void) = ReturnIndirection : &:r49_11, ~m49_13 -# 49| r49_16(glval) = VariableAddress[#return] : +# 49| v49_12(void) = ReturnIndirection : &:r49_6, m49_7 +# 49| v49_13(void) = ReturnIndirection : &:r49_10, m49_11 +# 49| r49_14(glval) = VariableAddress[#return] : # 49| valnum = r65_1 -# 49| v49_17(void) = ReturnValue : &:r49_16, m65_4 -# 49| v49_18(void) = UnmodeledUse : mu* -# 49| v49_19(void) = AliasedUse : ~m49_13 -# 49| v49_20(void) = ExitFunction : +# 49| v49_15(void) = ReturnValue : &:r49_14, m65_4 +# 49| v49_16(void) = UnmodeledUse : mu* +# 49| v49_17(void) = AliasedUse : ~m49_2 +# 49| v49_18(void) = ExitFunction : # 75| void test04(two_values*) # 75| Block 0 @@ -541,17 +535,15 @@ test.cpp: # 75| valnum = m75_5 # 75| m75_7(unknown) = InitializeIndirection[vals] : &:r75_6 # 75| valnum = unique -# 75| m75_8(unknown) = Chi : total:m75_2, partial:m75_7 -# 75| valnum = unique # 77| r77_1(glval) = VariableAddress[v] : # 77| valnum = r77_1 # 77| r77_2(glval) = FunctionAddress[getAValue] : # 77| valnum = unique # 77| r77_3(int) = Call : func:r77_2 # 77| valnum = unique -# 77| m77_4(unknown) = ^CallSideEffect : ~m75_8 +# 77| m77_4(unknown) = ^CallSideEffect : ~m75_2 # 77| valnum = unique -# 77| m77_5(unknown) = Chi : total:m75_8, partial:m77_4 +# 77| m77_5(unknown) = Chi : total:m75_2, partial:m77_4 # 77| valnum = unique # 77| r77_6(signed short) = Convert : r77_3 # 77| valnum = r77_6 @@ -569,7 +561,7 @@ test.cpp: # 79| valnum = m75_5 # 79| r79_6(glval) = FieldAddress[val1] : r79_5 # 79| valnum = unique -# 79| r79_7(signed short) = Load : &:r79_6, ~m77_5 +# 79| r79_7(signed short) = Load : &:r79_6, ~m75_7 # 79| valnum = unique # 79| r79_8(int) = Convert : r79_7 # 79| valnum = unique @@ -579,7 +571,7 @@ test.cpp: # 79| valnum = m75_5 # 79| r79_11(glval) = FieldAddress[val2] : r79_10 # 79| valnum = unique -# 79| r79_12(signed short) = Load : &:r79_11, ~m77_5 +# 79| r79_12(signed short) = Load : &:r79_11, ~m75_7 # 79| valnum = unique # 79| r79_13(int) = Convert : r79_12 # 79| valnum = unique @@ -612,11 +604,11 @@ test.cpp: # 82| m82_1(unknown) = Phi : from 0:~m77_5, from 1:~m80_4 # 82| valnum = unique # 82| v82_2(void) = NoOp : -# 75| v75_9(void) = ReturnIndirection : &:r75_6, ~m82_1 -# 75| v75_10(void) = ReturnVoid : -# 75| v75_11(void) = UnmodeledUse : mu* -# 75| v75_12(void) = AliasedUse : ~m82_1 -# 75| v75_13(void) = ExitFunction : +# 75| v75_8(void) = ReturnIndirection : &:r75_6, m75_7 +# 75| v75_9(void) = ReturnVoid : +# 75| v75_10(void) = UnmodeledUse : mu* +# 75| v75_11(void) = AliasedUse : ~m82_1 +# 75| v75_12(void) = ExitFunction : # 84| void test05(int, int, void*) # 84| Block 0 @@ -641,8 +633,6 @@ test.cpp: # 84| valnum = m84_9 # 84| m84_11(unknown) = InitializeIndirection[p] : &:r84_10 # 84| valnum = unique -# 84| m84_12(unknown) = Chi : total:m84_2, partial:m84_11 -# 84| valnum = unique # 86| r86_1(glval) = VariableAddress[v] : # 86| valnum = r86_1 # 86| m86_2(int) = Uninitialized[v] : &:r86_1 @@ -671,11 +661,11 @@ test.cpp: # 88| m88_10(int) = Store : &:r88_9, r88_8 # 88| valnum = m88_6 # 89| v89_1(void) = NoOp : -# 84| v84_13(void) = ReturnIndirection : &:r84_10, ~m84_12 -# 84| v84_14(void) = ReturnVoid : -# 84| v84_15(void) = UnmodeledUse : mu* -# 84| v84_16(void) = AliasedUse : ~m84_12 -# 84| v84_17(void) = ExitFunction : +# 84| v84_12(void) = ReturnIndirection : &:r84_10, m84_11 +# 84| v84_13(void) = ReturnVoid : +# 84| v84_14(void) = UnmodeledUse : mu* +# 84| v84_15(void) = AliasedUse : ~m84_2 +# 84| v84_16(void) = ExitFunction : # 88| Block 2 # 88| r88_11(glval) = VariableAddress[x] : @@ -748,8 +738,6 @@ test.cpp: # 104| valnum = m104_5 # 104| m104_7(unknown) = InitializeIndirection[pd] : &:r104_6 # 104| valnum = unique -# 104| m104_8(unknown) = Chi : total:m104_2, partial:m104_7 -# 104| valnum = unique # 105| r105_1(glval) = VariableAddress[x] : # 105| valnum = unique # 105| r105_2(glval) = VariableAddress[pd] : @@ -760,7 +748,7 @@ test.cpp: # 105| valnum = r105_4 # 105| r105_5(glval) = FieldAddress[b] : r105_4 # 105| valnum = r105_5 -# 105| r105_6(int) = Load : &:r105_5, ~m104_8 +# 105| r105_6(int) = Load : &:r105_5, ~m104_7 # 105| valnum = r105_6 # 105| m105_7(int) = Store : &:r105_1, r105_6 # 105| valnum = r105_6 @@ -782,7 +770,7 @@ test.cpp: # 107| valnum = r105_4 # 107| r107_4(glval) = FieldAddress[b] : r107_3 # 107| valnum = r105_5 -# 107| r107_5(int) = Load : &:r107_4, ~m104_8 +# 107| r107_5(int) = Load : &:r107_4, ~m104_7 # 107| valnum = r107_5 # 107| m107_6(int) = Store : &:r107_1, r107_5 # 107| valnum = r107_5 @@ -794,13 +782,13 @@ test.cpp: # 109| valnum = r107_5 # 109| m109_4(int) = Store : &:r109_1, r109_3 # 109| valnum = r107_5 -# 104| v104_9(void) = ReturnIndirection : &:r104_6, ~m104_8 -# 104| r104_10(glval) = VariableAddress[#return] : +# 104| v104_8(void) = ReturnIndirection : &:r104_6, m104_7 +# 104| r104_9(glval) = VariableAddress[#return] : # 104| valnum = r109_1 -# 104| v104_11(void) = ReturnValue : &:r104_10, m109_4 -# 104| v104_12(void) = UnmodeledUse : mu* -# 104| v104_13(void) = AliasedUse : ~m104_8 -# 104| v104_14(void) = ExitFunction : +# 104| v104_10(void) = ReturnValue : &:r104_9, m109_4 +# 104| v104_11(void) = UnmodeledUse : mu* +# 104| v104_12(void) = AliasedUse : ~m104_2 +# 104| v104_13(void) = ExitFunction : # 112| void test06() # 112| Block 0 From 542579de7f26eb29a1a5293fd9cd1bff16d3f137 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Tue, 28 Jan 2020 10:58:27 -0700 Subject: [PATCH 074/148] C++: Accept dataflow test changes due to new alias analysis --- .../library-tests/dataflow/dataflow-tests/BarrierGuard.cpp | 4 ++-- .../library-tests/dataflow/dataflow-tests/test_diff.expected | 3 --- .../library-tests/dataflow/dataflow-tests/test_ir.expected | 3 +++ 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp b/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp index 9c3c8bc4569..f2b91c00ed0 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp @@ -61,8 +61,8 @@ void bg_structptr(XY *p1, XY *p2) { if (guarded(p1->x)) { sink(p1->x); // no flow [FALSE POSITIVE in AST] } else if (guarded(p1->y)) { - sink(p1->x); // flow [NOT DETECTED in IR] + sink(p1->x); // flow } else if (guarded(p2->x)) { - sink(p1->x); // flow [NOT DETECTED in IR] + sink(p1->x); // flow } } diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected index e40505722af..31639764ad6 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected @@ -1,8 +1,5 @@ | BarrierGuard.cpp:60:11:60:16 | BarrierGuard.cpp:62:14:62:14 | AST only | -| BarrierGuard.cpp:60:11:60:16 | BarrierGuard.cpp:64:14:64:14 | AST only | -| BarrierGuard.cpp:60:11:60:16 | BarrierGuard.cpp:66:14:66:14 | AST only | | clang.cpp:12:9:12:20 | clang.cpp:22:8:22:20 | AST only | -| clang.cpp:28:27:28:32 | clang.cpp:29:27:29:28 | AST only | | clang.cpp:28:27:28:32 | clang.cpp:30:27:30:34 | AST only | | clang.cpp:39:42:39:47 | clang.cpp:41:18:41:19 | IR only | | dispatch.cpp:16:37:16:42 | dispatch.cpp:32:16:32:24 | IR only | diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_ir.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_ir.expected index 0e67184f477..bb20ccfc9d7 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_ir.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_ir.expected @@ -5,10 +5,13 @@ | BarrierGuard.cpp:33:10:33:15 | source | BarrierGuard.cpp:29:16:29:21 | source | | BarrierGuard.cpp:53:13:53:13 | x | BarrierGuard.cpp:49:10:49:15 | call to source | | BarrierGuard.cpp:55:13:55:13 | x | BarrierGuard.cpp:49:10:49:15 | call to source | +| BarrierGuard.cpp:64:14:64:14 | x | BarrierGuard.cpp:60:11:60:16 | call to source | +| BarrierGuard.cpp:66:14:66:14 | x | BarrierGuard.cpp:60:11:60:16 | call to source | | acrossLinkTargets.cpp:12:8:12:8 | (int)... | acrossLinkTargets.cpp:19:27:19:32 | call to source | | acrossLinkTargets.cpp:12:8:12:8 | x | acrossLinkTargets.cpp:19:27:19:32 | call to source | | clang.cpp:18:8:18:19 | (const int *)... | clang.cpp:12:9:12:20 | sourceArray1 | | clang.cpp:18:8:18:19 | sourceArray1 | clang.cpp:12:9:12:20 | sourceArray1 | +| clang.cpp:29:27:29:28 | m1 | clang.cpp:28:27:28:32 | call to source | | clang.cpp:37:10:37:11 | m2 | clang.cpp:34:32:34:37 | call to source | | clang.cpp:41:18:41:19 | m2 | clang.cpp:39:42:39:47 | call to source | | clang.cpp:45:17:45:18 | m2 | clang.cpp:43:35:43:40 | call to source | From bbcfbd7a28253478d6198277d63d170e35b7e38a Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Tue, 28 Jan 2020 22:34:01 +0100 Subject: [PATCH 075/148] Apply suggestion from code review --- java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp b/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp index 120eac2b531..3e2c8656399 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp @@ -11,10 +11,12 @@ is likely to be able to run malicious LDAP queries.

    If user input must be included in an LDAP query, it should be escaped to avoid a malicious user providing special characters that change the meaning -of the query. If possible build the LDAP query (or search filter / DN) using your -framework helper methods to avoid string concatenation, or escape user input -using the right LDAP encoding method, for example encodeForLDAP from OWASP ESAPI, -LdapEncoder from Spring LDAP or Filter.encodeValue from UnboundID library.

    +of the query. If possible build the LDAP query using framework helper methods, for example +from Spring's LdapQueryBuilder and LdapNameBuilder, +instead of string concatenation. Alternatively, escape user input using an appropriate +LDAP encoding method, for example: encodeForLDAP or
    encodeForDN +from OWASP ESAPI, LdapEncoder.filterEncode or LdapEncoder.nameEncode +from Spring LDAP, or Filter.encodeValue from UnboundID library.

    From 93910583634a868abf775bd6f4f7fb7cf40a7253 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 29 Jan 2020 11:37:33 +0100 Subject: [PATCH 076/148] Java: Add unit test for ldap injection. --- .../security/CWE-090/LdapInjection.expected | 231 +++++++++++++ .../security/CWE-090/LdapInjection.java | 326 ++++++++++++++++++ .../security/CWE-090/LdapInjection.qlref | 1 + .../test/query-tests/security/CWE-090/options | 1 + .../api/ldap/model/cursor/EntryCursor.java | 4 + .../api/ldap/model/cursor/SearchCursor.java | 4 + .../directory/api/ldap/model/entry/Value.java | 4 + .../ldap/model/exception/LdapException.java | 4 + .../exception/LdapInvalidDnException.java | 4 + .../api/ldap/model/filter/EqualityNode.java | 8 + .../api/ldap/model/filter/ExprNode.java | 4 + .../api/ldap/model/message/SearchRequest.java | 12 + .../ldap/model/message/SearchRequestImpl.java | 12 + .../api/ldap/model/message/SearchScope.java | 4 + .../directory/api/ldap/model/name/Dn.java | 8 + .../ldap/client/api/LdapConnection.java | 17 + .../client/api/LdapNetworkConnection.java | 9 + .../esapi-2.0.1/org/owasp/esapi/Encoder.java | 5 + .../owasp/esapi/reference/DefaultEncoder.java | 8 + .../ldap/core/ContextMapper.java | 4 + .../ldap/core/DirContextOperations.java | 4 + .../ldap/core/LdapTemplate.java | 28 ++ .../core/NameClassPairCallbackHandler.java | 3 + .../ldap/filter/EqualsFilter.java | 5 + .../springframework/ldap/filter/Filter.java | 4 + .../ldap/filter/HardcodedFilter.java | 6 + .../ldap/query/ConditionCriteria.java | 5 + .../ldap/query/ContainerCriteria.java | 4 + .../springframework/ldap/query/LdapQuery.java | 4 + .../ldap/query/LdapQueryBuilder.java | 14 + .../ldap/support/LdapEncoder.java | 5 + .../ldap/support/LdapNameBuilder.java | 12 + .../ldap/support/LdapUtils.java | 7 + .../web/bind/annotation/RequestParam.java | 8 + .../unboundid/ldap/sdk/AsyncRequestID.java | 3 + .../unboundid/ldap/sdk/DereferencePolicy.java | 3 + .../com/unboundid/ldap/sdk/Filter.java | 13 + .../unboundid/ldap/sdk/LDAPConnection.java | 21 ++ .../com/unboundid/ldap/sdk/LDAPException.java | 3 + .../ldap/sdk/LDAPSearchException.java | 3 + .../ldap/sdk/ReadOnlySearchRequest.java | 5 + .../com/unboundid/ldap/sdk/SearchRequest.java | 17 + .../com/unboundid/ldap/sdk/SearchResult.java | 3 + .../unboundid/ldap/sdk/SearchResultEntry.java | 3 + .../ldap/sdk/SearchResultListener.java | 3 + .../com/unboundid/ldap/sdk/SearchScope.java | 3 + 46 files changed, 859 insertions(+) create mode 100644 java/ql/test/query-tests/security/CWE-090/LdapInjection.expected create mode 100644 java/ql/test/query-tests/security/CWE-090/LdapInjection.java create mode 100644 java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref create mode 100644 java/ql/test/query-tests/security/CWE-090/options create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/EntryCursor.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/SearchCursor.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/entry/Value.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapException.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapInvalidDnException.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/EqualityNode.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/ExprNode.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequest.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequestImpl.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchScope.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/name/Dn.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapConnection.java create mode 100644 java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapNetworkConnection.java create mode 100644 java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/Encoder.java create mode 100644 java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/ContextMapper.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/DirContextOperations.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/LdapTemplate.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/NameClassPairCallbackHandler.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/EqualsFilter.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/Filter.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/HardcodedFilter.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ConditionCriteria.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ContainerCriteria.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQuery.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQueryBuilder.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapEncoder.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapNameBuilder.java create mode 100644 java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapUtils.java create mode 100644 java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/AsyncRequestID.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/DereferencePolicy.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/Filter.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPConnection.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPException.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPSearchException.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/ReadOnlySearchRequest.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchRequest.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResult.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultEntry.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultListener.java create mode 100644 java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchScope.java diff --git a/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected b/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected new file mode 100644 index 00000000000..e07c4ff05fd --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected @@ -0,0 +1,231 @@ +edges +| LdapInjection.java:41:28:41:52 | jBad : String | LdapInjection.java:43:38:43:57 | ... + ... | +| LdapInjection.java:41:55:41:81 | jBadDN : String | LdapInjection.java:43:16:43:35 | ... + ... | +| LdapInjection.java:46:28:46:52 | jBad : String | LdapInjection.java:48:56:48:75 | ... + ... | +| LdapInjection.java:46:55:46:85 | jBadDNName : String | LdapInjection.java:48:16:48:53 | new LdapName(...) | +| LdapInjection.java:51:28:51:52 | jBad : String | LdapInjection.java:53:63:53:82 | ... + ... | +| LdapInjection.java:56:28:56:59 | jBadInitial : String | LdapInjection.java:58:29:58:55 | ... + ... | +| LdapInjection.java:61:28:61:52 | jBad : String | LdapInjection.java:63:84:63:103 | ... + ... | +| LdapInjection.java:61:55:61:88 | jBadDNNameAdd : String | LdapInjection.java:63:16:63:81 | addAll(...) | +| LdapInjection.java:66:28:66:52 | jBad : String | LdapInjection.java:70:47:70:66 | ... + ... | +| LdapInjection.java:66:55:66:89 | jBadDNNameAdd2 : String | LdapInjection.java:70:16:70:44 | addAll(...) | +| LdapInjection.java:73:28:73:52 | jBad : String | LdapInjection.java:75:75:75:94 | ... + ... | +| LdapInjection.java:73:55:73:93 | jBadDNNameToString : String | LdapInjection.java:75:16:75:72 | toString(...) | +| LdapInjection.java:78:28:78:52 | jBad : String | LdapInjection.java:80:76:80:95 | ... + ... | +| LdapInjection.java:78:55:78:90 | jBadDNNameClone : String | LdapInjection.java:80:16:80:73 | (...)... | +| LdapInjection.java:92:31:92:55 | uBad : String | LdapInjection.java:94:67:94:86 | ... + ... | +| LdapInjection.java:92:58:92:84 | uBadDN : String | LdapInjection.java:94:20:94:39 | ... + ... | +| LdapInjection.java:97:31:97:67 | uBadFilterCreate : String | LdapInjection.java:98:58:98:88 | create(...) | +| LdapInjection.java:101:31:101:70 | uBadROSearchRequest : String | LdapInjection.java:105:14:105:14 | s | +| LdapInjection.java:101:73:101:103 | uBadROSRDN : String | LdapInjection.java:105:14:105:14 | s | +| LdapInjection.java:108:31:108:68 | uBadSearchRequest : String | LdapInjection.java:112:14:112:14 | s | +| LdapInjection.java:108:71:108:99 | uBadSRDN : String | LdapInjection.java:112:14:112:14 | s | +| LdapInjection.java:115:31:115:55 | uBad : String | LdapInjection.java:117:69:117:88 | ... + ... | +| LdapInjection.java:115:58:115:87 | uBadDNSFR : String | LdapInjection.java:117:22:117:44 | ... + ... | +| LdapInjection.java:120:31:120:75 | uBadROSearchRequestAsync : String | LdapInjection.java:124:19:124:19 | s | +| LdapInjection.java:120:78:120:113 | uBadROSRDNAsync : String | LdapInjection.java:124:19:124:19 | s | +| LdapInjection.java:127:31:127:73 | uBadSearchRequestAsync : String | LdapInjection.java:131:19:131:19 | s | +| LdapInjection.java:127:76:127:109 | uBadSRDNAsync : String | LdapInjection.java:131:19:131:19 | s | +| LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT : String | LdapInjection.java:135:58:135:115 | createNOTFilter(...) | +| LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer : String | LdapInjection.java:145:58:145:69 | toString(...) | +| LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate : String | LdapInjection.java:152:14:152:26 | duplicate(...) | +| LdapInjection.java:155:32:155:80 | uBadROSearchRequestDuplicate : String | LdapInjection.java:159:14:159:26 | duplicate(...) | +| LdapInjection.java:162:32:162:74 | uBadSearchRequestSetDN : String | LdapInjection.java:166:14:166:14 | s | +| LdapInjection.java:169:32:169:78 | uBadSearchRequestSetFilter : String | LdapInjection.java:173:14:173:14 | s | +| LdapInjection.java:197:30:197:54 | sBad : String | LdapInjection.java:198:36:198:55 | ... + ... | +| LdapInjection.java:197:57:197:83 | sBadDN : String | LdapInjection.java:198:14:198:33 | ... + ... | +| LdapInjection.java:201:30:201:54 | sBad : String | LdapInjection.java:202:88:202:107 | ... + ... | +| LdapInjection.java:201:57:201:92 | sBadDNLNBuilder : String | LdapInjection.java:202:20:202:85 | build(...) | +| LdapInjection.java:205:30:205:54 | sBad : String | LdapInjection.java:206:100:206:119 | ... + ... | +| LdapInjection.java:205:57:205:95 | sBadDNLNBuilderAdd : String | LdapInjection.java:206:23:206:97 | build(...) | +| LdapInjection.java:209:30:209:63 | sBadLdapQuery : String | LdapInjection.java:210:15:210:76 | filter(...) | +| LdapInjection.java:213:30:213:60 | sBadFilter : String | LdapInjection.java:214:66:214:112 | new HardcodedFilter(...) | +| LdapInjection.java:213:63:213:98 | sBadDNLdapUtils : String | LdapInjection.java:214:12:214:63 | newLdapName(...) | +| LdapInjection.java:217:30:217:63 | sBadLdapQuery : String | LdapInjection.java:218:24:218:85 | filter(...) | +| LdapInjection.java:221:30:221:64 | sBadLdapQuery2 : String | LdapInjection.java:223:24:223:24 | q | +| LdapInjection.java:226:30:226:73 | sBadLdapQueryWithFilter : String | LdapInjection.java:227:24:227:116 | filter(...) | +| LdapInjection.java:230:30:230:74 | sBadLdapQueryWithFilter2 : String | LdapInjection.java:232:24:232:57 | filter(...) | +| LdapInjection.java:235:31:235:68 | sBadLdapQueryBase : String | LdapInjection.java:236:12:236:66 | base(...) | +| LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex : String | LdapInjection.java:240:24:240:98 | is(...) | +| LdapInjection.java:247:31:247:67 | sBadFilterEncode : String | LdapInjection.java:250:18:250:29 | toString(...) | +| LdapInjection.java:266:30:266:54 | aBad : String | LdapInjection.java:268:36:268:55 | ... + ... | +| LdapInjection.java:266:57:266:83 | aBadDN : String | LdapInjection.java:268:14:268:33 | ... + ... | +| LdapInjection.java:276:30:276:67 | aBadSearchRequest : String | LdapInjection.java:280:14:280:14 | s | +| LdapInjection.java:283:74:283:103 | aBadDNObj : String | LdapInjection.java:287:14:287:14 | s | +| LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet : String | LdapInjection.java:294:14:294:24 | getBase(...) | +| LdapInjection.java:312:23:312:58 | okEncodeForLDAP : String | LdapInjection.java:314:61:314:75 | okEncodeForLDAP : String | +| LdapInjection.java:314:39:314:76 | encodeForLDAP(...) : String | LdapInjection.java:314:29:314:82 | ... + ... | +| LdapInjection.java:314:61:314:75 | okEncodeForLDAP : String | LdapInjection.java:314:39:314:76 | encodeForLDAP(...) : String | +| LdapInjection.java:318:23:318:57 | okFilterEncode : String | LdapInjection.java:319:64:319:77 | okFilterEncode : String | +| LdapInjection.java:319:39:319:78 | filterEncode(...) : String | LdapInjection.java:319:29:319:84 | ... + ... | +| LdapInjection.java:319:64:319:77 | okFilterEncode : String | LdapInjection.java:319:39:319:78 | filterEncode(...) : String | +nodes +| LdapInjection.java:41:28:41:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:41:55:41:81 | jBadDN : String | semmle.label | jBadDN : String | +| LdapInjection.java:43:16:43:35 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:43:38:43:57 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:46:28:46:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:46:55:46:85 | jBadDNName : String | semmle.label | jBadDNName : String | +| LdapInjection.java:48:16:48:53 | new LdapName(...) | semmle.label | new LdapName(...) | +| LdapInjection.java:48:56:48:75 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:51:28:51:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:53:63:53:82 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:56:28:56:59 | jBadInitial : String | semmle.label | jBadInitial : String | +| LdapInjection.java:58:29:58:55 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:61:28:61:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:61:55:61:88 | jBadDNNameAdd : String | semmle.label | jBadDNNameAdd : String | +| LdapInjection.java:63:16:63:81 | addAll(...) | semmle.label | addAll(...) | +| LdapInjection.java:63:84:63:103 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:66:28:66:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:66:55:66:89 | jBadDNNameAdd2 : String | semmle.label | jBadDNNameAdd2 : String | +| LdapInjection.java:70:16:70:44 | addAll(...) | semmle.label | addAll(...) | +| LdapInjection.java:70:47:70:66 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:73:28:73:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:73:55:73:93 | jBadDNNameToString : String | semmle.label | jBadDNNameToString : String | +| LdapInjection.java:75:16:75:72 | toString(...) | semmle.label | toString(...) | +| LdapInjection.java:75:75:75:94 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:78:28:78:52 | jBad : String | semmle.label | jBad : String | +| LdapInjection.java:78:55:78:90 | jBadDNNameClone : String | semmle.label | jBadDNNameClone : String | +| LdapInjection.java:80:16:80:73 | (...)... | semmle.label | (...)... | +| LdapInjection.java:80:76:80:95 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:92:31:92:55 | uBad : String | semmle.label | uBad : String | +| LdapInjection.java:92:58:92:84 | uBadDN : String | semmle.label | uBadDN : String | +| LdapInjection.java:94:20:94:39 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:94:67:94:86 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:97:31:97:67 | uBadFilterCreate : String | semmle.label | uBadFilterCreate : String | +| LdapInjection.java:98:58:98:88 | create(...) | semmle.label | create(...) | +| LdapInjection.java:101:31:101:70 | uBadROSearchRequest : String | semmle.label | uBadROSearchRequest : String | +| LdapInjection.java:101:73:101:103 | uBadROSRDN : String | semmle.label | uBadROSRDN : String | +| LdapInjection.java:105:14:105:14 | s | semmle.label | s | +| LdapInjection.java:108:31:108:68 | uBadSearchRequest : String | semmle.label | uBadSearchRequest : String | +| LdapInjection.java:108:71:108:99 | uBadSRDN : String | semmle.label | uBadSRDN : String | +| LdapInjection.java:112:14:112:14 | s | semmle.label | s | +| LdapInjection.java:115:31:115:55 | uBad : String | semmle.label | uBad : String | +| LdapInjection.java:115:58:115:87 | uBadDNSFR : String | semmle.label | uBadDNSFR : String | +| LdapInjection.java:117:22:117:44 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:117:69:117:88 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:120:31:120:75 | uBadROSearchRequestAsync : String | semmle.label | uBadROSearchRequestAsync : String | +| LdapInjection.java:120:78:120:113 | uBadROSRDNAsync : String | semmle.label | uBadROSRDNAsync : String | +| LdapInjection.java:124:19:124:19 | s | semmle.label | s | +| LdapInjection.java:127:31:127:73 | uBadSearchRequestAsync : String | semmle.label | uBadSearchRequestAsync : String | +| LdapInjection.java:127:76:127:109 | uBadSRDNAsync : String | semmle.label | uBadSRDNAsync : String | +| LdapInjection.java:131:19:131:19 | s | semmle.label | s | +| LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT : String | semmle.label | uBadFilterCreateNOT : String | +| LdapInjection.java:135:58:135:115 | createNOTFilter(...) | semmle.label | createNOTFilter(...) | +| LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer : String | semmle.label | uBadFilterCreateToStringBuffer : String | +| LdapInjection.java:145:58:145:69 | toString(...) | semmle.label | toString(...) | +| LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate : String | semmle.label | uBadSearchRequestDuplicate : String | +| LdapInjection.java:152:14:152:26 | duplicate(...) | semmle.label | duplicate(...) | +| LdapInjection.java:155:32:155:80 | uBadROSearchRequestDuplicate : String | semmle.label | uBadROSearchRequestDuplicate : String | +| LdapInjection.java:159:14:159:26 | duplicate(...) | semmle.label | duplicate(...) | +| LdapInjection.java:162:32:162:74 | uBadSearchRequestSetDN : String | semmle.label | uBadSearchRequestSetDN : String | +| LdapInjection.java:166:14:166:14 | s | semmle.label | s | +| LdapInjection.java:169:32:169:78 | uBadSearchRequestSetFilter : String | semmle.label | uBadSearchRequestSetFilter : String | +| LdapInjection.java:173:14:173:14 | s | semmle.label | s | +| LdapInjection.java:197:30:197:54 | sBad : String | semmle.label | sBad : String | +| LdapInjection.java:197:57:197:83 | sBadDN : String | semmle.label | sBadDN : String | +| LdapInjection.java:198:14:198:33 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:198:36:198:55 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:201:30:201:54 | sBad : String | semmle.label | sBad : String | +| LdapInjection.java:201:57:201:92 | sBadDNLNBuilder : String | semmle.label | sBadDNLNBuilder : String | +| LdapInjection.java:202:20:202:85 | build(...) | semmle.label | build(...) | +| LdapInjection.java:202:88:202:107 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:205:30:205:54 | sBad : String | semmle.label | sBad : String | +| LdapInjection.java:205:57:205:95 | sBadDNLNBuilderAdd : String | semmle.label | sBadDNLNBuilderAdd : String | +| LdapInjection.java:206:23:206:97 | build(...) | semmle.label | build(...) | +| LdapInjection.java:206:100:206:119 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:209:30:209:63 | sBadLdapQuery : String | semmle.label | sBadLdapQuery : String | +| LdapInjection.java:210:15:210:76 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:213:30:213:60 | sBadFilter : String | semmle.label | sBadFilter : String | +| LdapInjection.java:213:63:213:98 | sBadDNLdapUtils : String | semmle.label | sBadDNLdapUtils : String | +| LdapInjection.java:214:12:214:63 | newLdapName(...) | semmle.label | newLdapName(...) | +| LdapInjection.java:214:66:214:112 | new HardcodedFilter(...) | semmle.label | new HardcodedFilter(...) | +| LdapInjection.java:217:30:217:63 | sBadLdapQuery : String | semmle.label | sBadLdapQuery : String | +| LdapInjection.java:218:24:218:85 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:221:30:221:64 | sBadLdapQuery2 : String | semmle.label | sBadLdapQuery2 : String | +| LdapInjection.java:223:24:223:24 | q | semmle.label | q | +| LdapInjection.java:226:30:226:73 | sBadLdapQueryWithFilter : String | semmle.label | sBadLdapQueryWithFilter : String | +| LdapInjection.java:227:24:227:116 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:230:30:230:74 | sBadLdapQueryWithFilter2 : String | semmle.label | sBadLdapQueryWithFilter2 : String | +| LdapInjection.java:232:24:232:57 | filter(...) | semmle.label | filter(...) | +| LdapInjection.java:235:31:235:68 | sBadLdapQueryBase : String | semmle.label | sBadLdapQueryBase : String | +| LdapInjection.java:236:12:236:66 | base(...) | semmle.label | base(...) | +| LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex : String | semmle.label | sBadLdapQueryComplex : String | +| LdapInjection.java:240:24:240:98 | is(...) | semmle.label | is(...) | +| LdapInjection.java:247:31:247:67 | sBadFilterEncode : String | semmle.label | sBadFilterEncode : String | +| LdapInjection.java:250:18:250:29 | toString(...) | semmle.label | toString(...) | +| LdapInjection.java:266:30:266:54 | aBad : String | semmle.label | aBad : String | +| LdapInjection.java:266:57:266:83 | aBadDN : String | semmle.label | aBadDN : String | +| LdapInjection.java:268:14:268:33 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:268:36:268:55 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:276:30:276:67 | aBadSearchRequest : String | semmle.label | aBadSearchRequest : String | +| LdapInjection.java:280:14:280:14 | s | semmle.label | s | +| LdapInjection.java:283:74:283:103 | aBadDNObj : String | semmle.label | aBadDNObj : String | +| LdapInjection.java:287:14:287:14 | s | semmle.label | s | +| LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet : String | semmle.label | aBadDNSearchRequestGet : String | +| LdapInjection.java:294:14:294:24 | getBase(...) | semmle.label | getBase(...) | +| LdapInjection.java:312:23:312:58 | okEncodeForLDAP : String | semmle.label | okEncodeForLDAP : String | +| LdapInjection.java:314:29:314:82 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:314:39:314:76 | encodeForLDAP(...) : String | semmle.label | encodeForLDAP(...) : String | +| LdapInjection.java:314:61:314:75 | okEncodeForLDAP : String | semmle.label | okEncodeForLDAP : String | +| LdapInjection.java:318:23:318:57 | okFilterEncode : String | semmle.label | okFilterEncode : String | +| LdapInjection.java:319:29:319:84 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:319:39:319:78 | filterEncode(...) : String | semmle.label | filterEncode(...) : String | +| LdapInjection.java:319:64:319:77 | okFilterEncode : String | semmle.label | okFilterEncode : String | +#select +| LdapInjection.java:43:16:43:35 | ... + ... | LdapInjection.java:41:55:41:81 | jBadDN : String | LdapInjection.java:43:16:43:35 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:41:55:41:81 | jBadDN | this user input | +| LdapInjection.java:43:38:43:57 | ... + ... | LdapInjection.java:41:28:41:52 | jBad : String | LdapInjection.java:43:38:43:57 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:41:28:41:52 | jBad | this user input | +| LdapInjection.java:48:16:48:53 | new LdapName(...) | LdapInjection.java:46:55:46:85 | jBadDNName : String | LdapInjection.java:48:16:48:53 | new LdapName(...) | LDAP query might include code from $@. | LdapInjection.java:46:55:46:85 | jBadDNName | this user input | +| LdapInjection.java:48:56:48:75 | ... + ... | LdapInjection.java:46:28:46:52 | jBad : String | LdapInjection.java:48:56:48:75 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:46:28:46:52 | jBad | this user input | +| LdapInjection.java:53:63:53:82 | ... + ... | LdapInjection.java:51:28:51:52 | jBad : String | LdapInjection.java:53:63:53:82 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:51:28:51:52 | jBad | this user input | +| LdapInjection.java:58:29:58:55 | ... + ... | LdapInjection.java:56:28:56:59 | jBadInitial : String | LdapInjection.java:58:29:58:55 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:56:28:56:59 | jBadInitial | this user input | +| LdapInjection.java:63:16:63:81 | addAll(...) | LdapInjection.java:61:55:61:88 | jBadDNNameAdd : String | LdapInjection.java:63:16:63:81 | addAll(...) | LDAP query might include code from $@. | LdapInjection.java:61:55:61:88 | jBadDNNameAdd | this user input | +| LdapInjection.java:63:84:63:103 | ... + ... | LdapInjection.java:61:28:61:52 | jBad : String | LdapInjection.java:63:84:63:103 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:61:28:61:52 | jBad | this user input | +| LdapInjection.java:70:16:70:44 | addAll(...) | LdapInjection.java:66:55:66:89 | jBadDNNameAdd2 : String | LdapInjection.java:70:16:70:44 | addAll(...) | LDAP query might include code from $@. | LdapInjection.java:66:55:66:89 | jBadDNNameAdd2 | this user input | +| LdapInjection.java:70:47:70:66 | ... + ... | LdapInjection.java:66:28:66:52 | jBad : String | LdapInjection.java:70:47:70:66 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:66:28:66:52 | jBad | this user input | +| LdapInjection.java:75:16:75:72 | toString(...) | LdapInjection.java:73:55:73:93 | jBadDNNameToString : String | LdapInjection.java:75:16:75:72 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:73:55:73:93 | jBadDNNameToString | this user input | +| LdapInjection.java:75:75:75:94 | ... + ... | LdapInjection.java:73:28:73:52 | jBad : String | LdapInjection.java:75:75:75:94 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:73:28:73:52 | jBad | this user input | +| LdapInjection.java:80:16:80:73 | (...)... | LdapInjection.java:78:55:78:90 | jBadDNNameClone : String | LdapInjection.java:80:16:80:73 | (...)... | LDAP query might include code from $@. | LdapInjection.java:78:55:78:90 | jBadDNNameClone | this user input | +| LdapInjection.java:80:76:80:95 | ... + ... | LdapInjection.java:78:28:78:52 | jBad : String | LdapInjection.java:80:76:80:95 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:78:28:78:52 | jBad | this user input | +| LdapInjection.java:94:20:94:39 | ... + ... | LdapInjection.java:92:58:92:84 | uBadDN : String | LdapInjection.java:94:20:94:39 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:92:58:92:84 | uBadDN | this user input | +| LdapInjection.java:94:67:94:86 | ... + ... | LdapInjection.java:92:31:92:55 | uBad : String | LdapInjection.java:94:67:94:86 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:92:31:92:55 | uBad | this user input | +| LdapInjection.java:98:58:98:88 | create(...) | LdapInjection.java:97:31:97:67 | uBadFilterCreate : String | LdapInjection.java:98:58:98:88 | create(...) | LDAP query might include code from $@. | LdapInjection.java:97:31:97:67 | uBadFilterCreate | this user input | +| LdapInjection.java:105:14:105:14 | s | LdapInjection.java:101:31:101:70 | uBadROSearchRequest : String | LdapInjection.java:105:14:105:14 | s | LDAP query might include code from $@. | LdapInjection.java:101:31:101:70 | uBadROSearchRequest | this user input | +| LdapInjection.java:105:14:105:14 | s | LdapInjection.java:101:73:101:103 | uBadROSRDN : String | LdapInjection.java:105:14:105:14 | s | LDAP query might include code from $@. | LdapInjection.java:101:73:101:103 | uBadROSRDN | this user input | +| LdapInjection.java:112:14:112:14 | s | LdapInjection.java:108:31:108:68 | uBadSearchRequest : String | LdapInjection.java:112:14:112:14 | s | LDAP query might include code from $@. | LdapInjection.java:108:31:108:68 | uBadSearchRequest | this user input | +| LdapInjection.java:112:14:112:14 | s | LdapInjection.java:108:71:108:99 | uBadSRDN : String | LdapInjection.java:112:14:112:14 | s | LDAP query might include code from $@. | LdapInjection.java:108:71:108:99 | uBadSRDN | this user input | +| LdapInjection.java:117:22:117:44 | ... + ... | LdapInjection.java:115:58:115:87 | uBadDNSFR : String | LdapInjection.java:117:22:117:44 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:115:58:115:87 | uBadDNSFR | this user input | +| LdapInjection.java:117:69:117:88 | ... + ... | LdapInjection.java:115:31:115:55 | uBad : String | LdapInjection.java:117:69:117:88 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:115:31:115:55 | uBad | this user input | +| LdapInjection.java:124:19:124:19 | s | LdapInjection.java:120:31:120:75 | uBadROSearchRequestAsync : String | LdapInjection.java:124:19:124:19 | s | LDAP query might include code from $@. | LdapInjection.java:120:31:120:75 | uBadROSearchRequestAsync | this user input | +| LdapInjection.java:124:19:124:19 | s | LdapInjection.java:120:78:120:113 | uBadROSRDNAsync : String | LdapInjection.java:124:19:124:19 | s | LDAP query might include code from $@. | LdapInjection.java:120:78:120:113 | uBadROSRDNAsync | this user input | +| LdapInjection.java:131:19:131:19 | s | LdapInjection.java:127:31:127:73 | uBadSearchRequestAsync : String | LdapInjection.java:131:19:131:19 | s | LDAP query might include code from $@. | LdapInjection.java:127:31:127:73 | uBadSearchRequestAsync | this user input | +| LdapInjection.java:131:19:131:19 | s | LdapInjection.java:127:76:127:109 | uBadSRDNAsync : String | LdapInjection.java:131:19:131:19 | s | LDAP query might include code from $@. | LdapInjection.java:127:76:127:109 | uBadSRDNAsync | this user input | +| LdapInjection.java:135:58:135:115 | createNOTFilter(...) | LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT : String | LdapInjection.java:135:58:135:115 | createNOTFilter(...) | LDAP query might include code from $@. | LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT | this user input | +| LdapInjection.java:145:58:145:69 | toString(...) | LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer : String | LdapInjection.java:145:58:145:69 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer | this user input | +| LdapInjection.java:152:14:152:26 | duplicate(...) | LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate : String | LdapInjection.java:152:14:152:26 | duplicate(...) | LDAP query might include code from $@. | LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate | this user input | +| LdapInjection.java:159:14:159:26 | duplicate(...) | LdapInjection.java:155:32:155:80 | uBadROSearchRequestDuplicate : String | LdapInjection.java:159:14:159:26 | duplicate(...) | LDAP query might include code from $@. | LdapInjection.java:155:32:155:80 | uBadROSearchRequestDuplicate | this user input | +| LdapInjection.java:166:14:166:14 | s | LdapInjection.java:162:32:162:74 | uBadSearchRequestSetDN : String | LdapInjection.java:166:14:166:14 | s | LDAP query might include code from $@. | LdapInjection.java:162:32:162:74 | uBadSearchRequestSetDN | this user input | +| LdapInjection.java:173:14:173:14 | s | LdapInjection.java:169:32:169:78 | uBadSearchRequestSetFilter : String | LdapInjection.java:173:14:173:14 | s | LDAP query might include code from $@. | LdapInjection.java:169:32:169:78 | uBadSearchRequestSetFilter | this user input | +| LdapInjection.java:198:14:198:33 | ... + ... | LdapInjection.java:197:57:197:83 | sBadDN : String | LdapInjection.java:198:14:198:33 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:197:57:197:83 | sBadDN | this user input | +| LdapInjection.java:198:36:198:55 | ... + ... | LdapInjection.java:197:30:197:54 | sBad : String | LdapInjection.java:198:36:198:55 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:197:30:197:54 | sBad | this user input | +| LdapInjection.java:202:20:202:85 | build(...) | LdapInjection.java:201:57:201:92 | sBadDNLNBuilder : String | LdapInjection.java:202:20:202:85 | build(...) | LDAP query might include code from $@. | LdapInjection.java:201:57:201:92 | sBadDNLNBuilder | this user input | +| LdapInjection.java:202:88:202:107 | ... + ... | LdapInjection.java:201:30:201:54 | sBad : String | LdapInjection.java:202:88:202:107 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:201:30:201:54 | sBad | this user input | +| LdapInjection.java:206:23:206:97 | build(...) | LdapInjection.java:205:57:205:95 | sBadDNLNBuilderAdd : String | LdapInjection.java:206:23:206:97 | build(...) | LDAP query might include code from $@. | LdapInjection.java:205:57:205:95 | sBadDNLNBuilderAdd | this user input | +| LdapInjection.java:206:100:206:119 | ... + ... | LdapInjection.java:205:30:205:54 | sBad : String | LdapInjection.java:206:100:206:119 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:205:30:205:54 | sBad | this user input | +| LdapInjection.java:210:15:210:76 | filter(...) | LdapInjection.java:209:30:209:63 | sBadLdapQuery : String | LdapInjection.java:210:15:210:76 | filter(...) | LDAP query might include code from $@. | LdapInjection.java:209:30:209:63 | sBadLdapQuery | this user input | +| LdapInjection.java:214:12:214:63 | newLdapName(...) | LdapInjection.java:213:63:213:98 | sBadDNLdapUtils : String | LdapInjection.java:214:12:214:63 | newLdapName(...) | LDAP query might include code from $@. | LdapInjection.java:213:63:213:98 | sBadDNLdapUtils | this user input | +| LdapInjection.java:214:66:214:112 | new HardcodedFilter(...) | LdapInjection.java:213:30:213:60 | sBadFilter : String | LdapInjection.java:214:66:214:112 | new HardcodedFilter(...) | LDAP query might include code from $@. | LdapInjection.java:213:30:213:60 | sBadFilter | this user input | +| LdapInjection.java:218:24:218:85 | filter(...) | LdapInjection.java:217:30:217:63 | sBadLdapQuery : String | LdapInjection.java:218:24:218:85 | filter(...) | LDAP query might include code from $@. | LdapInjection.java:217:30:217:63 | sBadLdapQuery | this user input | +| LdapInjection.java:223:24:223:24 | q | LdapInjection.java:221:30:221:64 | sBadLdapQuery2 : String | LdapInjection.java:223:24:223:24 | q | LDAP query might include code from $@. | LdapInjection.java:221:30:221:64 | sBadLdapQuery2 | this user input | +| LdapInjection.java:227:24:227:116 | filter(...) | LdapInjection.java:226:30:226:73 | sBadLdapQueryWithFilter : String | LdapInjection.java:227:24:227:116 | filter(...) | LDAP query might include code from $@. | LdapInjection.java:226:30:226:73 | sBadLdapQueryWithFilter | this user input | +| LdapInjection.java:232:24:232:57 | filter(...) | LdapInjection.java:230:30:230:74 | sBadLdapQueryWithFilter2 : String | LdapInjection.java:232:24:232:57 | filter(...) | LDAP query might include code from $@. | LdapInjection.java:230:30:230:74 | sBadLdapQueryWithFilter2 | this user input | +| LdapInjection.java:236:12:236:66 | base(...) | LdapInjection.java:235:31:235:68 | sBadLdapQueryBase : String | LdapInjection.java:236:12:236:66 | base(...) | LDAP query might include code from $@. | LdapInjection.java:235:31:235:68 | sBadLdapQueryBase | this user input | +| LdapInjection.java:240:24:240:98 | is(...) | LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex : String | LdapInjection.java:240:24:240:98 | is(...) | LDAP query might include code from $@. | LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex | this user input | +| LdapInjection.java:250:18:250:29 | toString(...) | LdapInjection.java:247:31:247:67 | sBadFilterEncode : String | LdapInjection.java:250:18:250:29 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:247:31:247:67 | sBadFilterEncode | this user input | +| LdapInjection.java:268:14:268:33 | ... + ... | LdapInjection.java:266:57:266:83 | aBadDN : String | LdapInjection.java:268:14:268:33 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:266:57:266:83 | aBadDN | this user input | +| LdapInjection.java:268:36:268:55 | ... + ... | LdapInjection.java:266:30:266:54 | aBad : String | LdapInjection.java:268:36:268:55 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:266:30:266:54 | aBad | this user input | +| LdapInjection.java:280:14:280:14 | s | LdapInjection.java:276:30:276:67 | aBadSearchRequest : String | LdapInjection.java:280:14:280:14 | s | LDAP query might include code from $@. | LdapInjection.java:276:30:276:67 | aBadSearchRequest | this user input | +| LdapInjection.java:287:14:287:14 | s | LdapInjection.java:283:74:283:103 | aBadDNObj : String | LdapInjection.java:287:14:287:14 | s | LDAP query might include code from $@. | LdapInjection.java:283:74:283:103 | aBadDNObj | this user input | +| LdapInjection.java:294:14:294:24 | getBase(...) | LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet : String | LdapInjection.java:294:14:294:24 | getBase(...) | LDAP query might include code from $@. | LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet | this user input | +| LdapInjection.java:314:29:314:82 | ... + ... | LdapInjection.java:312:23:312:58 | okEncodeForLDAP : String | LdapInjection.java:314:29:314:82 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:312:23:312:58 | okEncodeForLDAP | this user input | +| LdapInjection.java:319:29:319:84 | ... + ... | LdapInjection.java:318:23:318:57 | okFilterEncode : String | LdapInjection.java:319:29:319:84 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:318:23:318:57 | okFilterEncode | this user input | diff --git a/java/ql/test/query-tests/security/CWE-090/LdapInjection.java b/java/ql/test/query-tests/security/CWE-090/LdapInjection.java new file mode 100644 index 00000000000..1680664d872 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-090/LdapInjection.java @@ -0,0 +1,326 @@ +import java.util.List; + +import javax.naming.Name; +import javax.naming.NamingException; +import javax.naming.directory.BasicAttributes; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import javax.naming.directory.SearchControls; +import javax.naming.ldap.InitialLdapContext; +import javax.naming.ldap.LdapContext; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +import com.unboundid.ldap.sdk.Filter; +import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.LDAPSearchException; +import com.unboundid.ldap.sdk.ReadOnlySearchRequest; +import com.unboundid.ldap.sdk.SearchRequest; + +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.filter.EqualityNode; +import org.apache.directory.api.ldap.model.message.SearchRequestImpl; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.ldap.client.api.LdapConnection; +import org.apache.directory.ldap.client.api.LdapNetworkConnection; +import org.owasp.esapi.Encoder; +import org.owasp.esapi.reference.DefaultEncoder; +import org.springframework.ldap.core.LdapTemplate; +import org.springframework.ldap.filter.EqualsFilter; +import org.springframework.ldap.filter.HardcodedFilter; +import org.springframework.ldap.query.LdapQuery; +import org.springframework.ldap.query.LdapQueryBuilder; +import org.springframework.ldap.support.LdapEncoder; +import org.springframework.ldap.support.LdapNameBuilder; +import org.springframework.ldap.support.LdapUtils; +import org.springframework.web.bind.annotation.RequestParam; + +public class LdapInjection { + // JNDI + public void testJndiBad1(@RequestParam String jBad, @RequestParam String jBadDN, DirContext ctx) + throws NamingException { + ctx.search("ou=system" + jBadDN, "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad2(@RequestParam String jBad, @RequestParam String jBadDNName, InitialDirContext ctx) + throws NamingException { + ctx.search(new LdapName("ou=system" + jBadDNName), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad3(@RequestParam String jBad, @RequestParam String jOkDN, LdapContext ctx) + throws NamingException { + ctx.search(new LdapName(List.of(new Rdn("ou=" + jOkDN))), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad4(@RequestParam String jBadInitial, InitialLdapContext ctx) + throws NamingException { + ctx.search("ou=system", "(uid=" + jBadInitial + ")", new SearchControls()); + } + + public void testJndiBad5(@RequestParam String jBad, @RequestParam String jBadDNNameAdd, InitialDirContext ctx) + throws NamingException { + ctx.search(new LdapName("").addAll(new LdapName("ou=system" + jBadDNNameAdd)), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad6(@RequestParam String jBad, @RequestParam String jBadDNNameAdd2, InitialDirContext ctx) + throws NamingException { + LdapName name = new LdapName(""); + name.addAll(new LdapName("ou=system" + jBadDNNameAdd2).getRdns()); + ctx.search(new LdapName("").addAll(name), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad7(@RequestParam String jBad, @RequestParam String jBadDNNameToString, InitialDirContext ctx) + throws NamingException { + ctx.search(new LdapName("ou=system" + jBadDNNameToString).toString(), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiBad8(@RequestParam String jBad, @RequestParam String jBadDNNameClone, InitialDirContext ctx) + throws NamingException { + ctx.search((Name) new LdapName("ou=system" + jBadDNNameClone).clone(), "(uid=" + jBad + ")", new SearchControls()); + } + + public void testJndiOk1(@RequestParam String jOkFilterExpr, DirContext ctx) throws NamingException { + ctx.search("ou=system", "(uid={0})", new String[] { jOkFilterExpr }, new SearchControls()); + } + + public void testJndiOk2(@RequestParam String jOkAttribute, DirContext ctx) throws NamingException { + ctx.search("ou=system", new BasicAttributes(jOkAttribute, jOkAttribute)); + } + + // UnboundID + public void testUnboundBad1(@RequestParam String uBad, @RequestParam String uBadDN, LDAPConnection c) + throws LDAPSearchException { + c.search(null, "ou=system" + uBadDN, null, null, 1, 1, false, "(uid=" + uBad + ")"); + } + + public void testUnboundBad2(@RequestParam String uBadFilterCreate, LDAPConnection c) throws LDAPException { + c.search(null, "ou=system", null, null, 1, 1, false, Filter.create(uBadFilterCreate)); + } + + public void testUnboundBad3(@RequestParam String uBadROSearchRequest, @RequestParam String uBadROSRDN, + LDAPConnection c) throws LDAPException { + ReadOnlySearchRequest s = new SearchRequest(null, "ou=system" + uBadROSRDN, null, null, 1, 1, false, + "(uid=" + uBadROSearchRequest + ")"); + c.search(s); + } + + public void testUnboundBad4(@RequestParam String uBadSearchRequest, @RequestParam String uBadSRDN, LDAPConnection c) + throws LDAPException { + SearchRequest s = new SearchRequest(null, "ou=system" + uBadSRDN, null, null, 1, 1, false, + "(uid=" + uBadSearchRequest + ")"); + c.search(s); + } + + public void testUnboundBad5(@RequestParam String uBad, @RequestParam String uBadDNSFR, LDAPConnection c) + throws LDAPSearchException { + c.searchForEntry("ou=system" + uBadDNSFR, null, null, 1, false, "(uid=" + uBad + ")"); + } + + public void testUnboundBad6(@RequestParam String uBadROSearchRequestAsync, @RequestParam String uBadROSRDNAsync, + LDAPConnection c) throws LDAPException { + ReadOnlySearchRequest s = new SearchRequest(null, "ou=system" + uBadROSRDNAsync, null, null, 1, 1, false, + "(uid=" + uBadROSearchRequestAsync + ")"); + c.asyncSearch(s); + } + + public void testUnboundBad7(@RequestParam String uBadSearchRequestAsync, @RequestParam String uBadSRDNAsync, LDAPConnection c) + throws LDAPException { + SearchRequest s = new SearchRequest(null, "ou=system" + uBadSRDNAsync, null, null, 1, 1, false, + "(uid=" + uBadSearchRequestAsync + ")"); + c.asyncSearch(s); + } + + public void testUnboundBad8(@RequestParam String uBadFilterCreateNOT, LDAPConnection c) throws LDAPException { + c.search(null, "ou=system", null, null, 1, 1, false, Filter.createNOTFilter(Filter.create(uBadFilterCreateNOT))); + } + + public void testUnboundBad9(@RequestParam String uBadFilterCreateToString, LDAPConnection c) throws LDAPException { + c.search(null, "ou=system", null, null, 1, 1, false, Filter.create(uBadFilterCreateToString).toString()); // False Negative + } + + public void testUnboundBad10(@RequestParam String uBadFilterCreateToStringBuffer, LDAPConnection c) throws LDAPException { + StringBuilder b = new StringBuilder(); + Filter.create(uBadFilterCreateToStringBuffer).toNormalizedString(b); + c.search(null, "ou=system", null, null, 1, 1, false, b.toString()); + } + + public void testUnboundBad11(@RequestParam String uBadSearchRequestDuplicate, LDAPConnection c) + throws LDAPException { + SearchRequest s = new SearchRequest(null, "ou=system", null, null, 1, 1, false, + "(uid=" + uBadSearchRequestDuplicate + ")"); + c.search(s.duplicate()); + } + + public void testUnboundBad12(@RequestParam String uBadROSearchRequestDuplicate, LDAPConnection c) + throws LDAPException { + ReadOnlySearchRequest s = new SearchRequest(null, "ou=system", null, null, 1, 1, false, + "(uid=" + uBadROSearchRequestDuplicate + ")"); + c.search(s.duplicate()); + } + + public void testUnboundBad13(@RequestParam String uBadSearchRequestSetDN, LDAPConnection c) + throws LDAPException { + SearchRequest s = new SearchRequest(null, "", null, null, 1, 1, false, ""); + s.setBaseDN(uBadSearchRequestSetDN); + c.search(s); + } + + public void testUnboundBad14(@RequestParam String uBadSearchRequestSetFilter, LDAPConnection c) + throws LDAPException { + SearchRequest s = new SearchRequest(null, "ou=system", null, null, 1, 1, false, ""); + s.setFilter(uBadSearchRequestSetFilter); + c.search(s); + } + + public void testUnboundOk1(@RequestParam String uOkEqualityFilter, LDAPConnection c) throws LDAPSearchException { + c.search(null, "ou=system", null, null, 1, 1, false, Filter.createEqualityFilter("uid", uOkEqualityFilter)); + } + + public void testUnboundOk2(@RequestParam String uOkVaragsAttr, LDAPConnection c) throws LDAPSearchException { + c.search("ou=system", null, null, 1, 1, false, "(uid=fixed)", "a" + uOkVaragsAttr); + } + + public void testUnboundOk3(@RequestParam String uOkFilterSearchRequest, LDAPConnection c) throws LDAPException { + SearchRequest s = new SearchRequest(null, "ou=system", null, null, 1, 1, false, + Filter.createEqualityFilter("uid", uOkFilterSearchRequest)); + c.search(s); + } + + public void testUnboundOk4(@RequestParam String uOkSearchRequestVarargs, LDAPConnection c) throws LDAPException { + SearchRequest s = new SearchRequest("ou=system", null, "(uid=fixed)", "va1", "va2", "va3", + "a" + uOkSearchRequestVarargs); + c.search(s); + } + + // Spring LDAP + public void testSpringBad1(@RequestParam String sBad, @RequestParam String sBadDN, LdapTemplate c) { + c.search("ou=system" + sBadDN, "(uid=" + sBad + ")", 1, false, null); + } + + public void testSpringBad2(@RequestParam String sBad, @RequestParam String sBadDNLNBuilder, LdapTemplate c) { + c.authenticate(LdapNameBuilder.newInstance("ou=system" + sBadDNLNBuilder).build(), "(uid=" + sBad + ")", "pass"); + } + + public void testSpringBad3(@RequestParam String sBad, @RequestParam String sBadDNLNBuilderAdd, LdapTemplate c) { + c.searchForObject(LdapNameBuilder.newInstance().add("ou=system" + sBadDNLNBuilderAdd).build(), "(uid=" + sBad + ")", null); + } + + public void testSpringBad4(@RequestParam String sBadLdapQuery, LdapTemplate c) { + c.findOne(LdapQueryBuilder.query().filter("(uid=" + sBadLdapQuery + ")"), null); + } + + public void testSpringBad5(@RequestParam String sBadFilter, @RequestParam String sBadDNLdapUtils, LdapTemplate c) { + c.find(LdapUtils.newLdapName("ou=system" + sBadDNLdapUtils), new HardcodedFilter("(uid=" + sBadFilter + ")"), null, null); + } + + public void testSpringBad6(@RequestParam String sBadLdapQuery, LdapTemplate c) { + c.searchForContext(LdapQueryBuilder.query().filter("(uid=" + sBadLdapQuery + ")")); + } + + public void testSpringBad7(@RequestParam String sBadLdapQuery2, LdapTemplate c) { + LdapQuery q = LdapQueryBuilder.query().filter("(uid=" + sBadLdapQuery2 + ")"); + c.searchForContext(q); + } + + public void testSpringBad8(@RequestParam String sBadLdapQueryWithFilter, LdapTemplate c) { + c.searchForContext(LdapQueryBuilder.query().filter(new HardcodedFilter("(uid=" + sBadLdapQueryWithFilter + ")"))); + } + + public void testSpringBad9(@RequestParam String sBadLdapQueryWithFilter2, LdapTemplate c) { + org.springframework.ldap.filter.Filter f = new HardcodedFilter("(uid=" + sBadLdapQueryWithFilter2 + ")"); + c.searchForContext(LdapQueryBuilder.query().filter(f)); + } + + public void testSpringBad10(@RequestParam String sBadLdapQueryBase, LdapTemplate c) { + c.find(LdapQueryBuilder.query().base(sBadLdapQueryBase).base(), null, null, null); + } + + public void testSpringBad11(@RequestParam String sBadLdapQueryComplex, LdapTemplate c) { + c.searchForContext(LdapQueryBuilder.query().base(sBadLdapQueryComplex).where("uid").is("test")); + } + + public void testSpringBad12(@RequestParam String sBadFilterToString, LdapTemplate c) { + c.search("", new HardcodedFilter("(uid=" + sBadFilterToString + ")").toString(), 1, false, null); // False Negative + } + + public void testSpringBad13(@RequestParam String sBadFilterEncode, LdapTemplate c) { + StringBuffer s = new StringBuffer(); + new HardcodedFilter("(uid=" + sBadFilterEncode + ")").encode(s); + c.search("", s.toString(), 1, false, null); + } + + public void testSpringOk1(@RequestParam String sOkLdapQuery, LdapTemplate c) { + c.find(LdapQueryBuilder.query().filter("(uid={0})", sOkLdapQuery), null); + } + + public void testSpringOk2(@RequestParam String sOkFilter, @RequestParam String sOkDN, LdapTemplate c) { + c.find(LdapNameBuilder.newInstance().add("ou", sOkDN).build(), new EqualsFilter("uid", sOkFilter), null, null); + } + + public void testSpringOk3(@RequestParam String sOkLdapQuery, @RequestParam String sOkPassword, LdapTemplate c) { + c.authenticate(LdapQueryBuilder.query().filter("(uid={0})", sOkLdapQuery), sOkPassword); + } + + // Apache LDAP API + public void testApacheBad1(@RequestParam String aBad, @RequestParam String aBadDN, LdapConnection c) + throws LdapException { + c.search("ou=system" + aBadDN, "(uid=" + aBad + ")", null); + } + + public void testApacheBad2(@RequestParam String aBad, @RequestParam String aBadDNObjToString, LdapNetworkConnection c) + throws LdapException { + c.search(new Dn("ou=system" + aBadDNObjToString).getName(), "(uid=" + aBad + ")", null); // False Negative + } + + public void testApacheBad3(@RequestParam String aBadSearchRequest, LdapConnection c) + throws LdapException { + org.apache.directory.api.ldap.model.message.SearchRequest s = new SearchRequestImpl(); + s.setFilter("(uid=" + aBadSearchRequest + ")"); + c.search(s); + } + + public void testApacheBad4(@RequestParam String aBadSearchRequestImpl, @RequestParam String aBadDNObj, LdapConnection c) + throws LdapException { + SearchRequestImpl s = new SearchRequestImpl(); + s.setBase(new Dn("ou=system" + aBadDNObj)); + c.search(s); + } + + public void testApacheBad5(@RequestParam String aBadDNSearchRequestGet, LdapConnection c) + throws LdapException { + org.apache.directory.api.ldap.model.message.SearchRequest s = new SearchRequestImpl(); + s.setBase(new Dn("ou=system" + aBadDNSearchRequestGet)); + c.search(s.getBase(), "(uid=test", null); + } + + public void testApacheOk1(@RequestParam String aOk, LdapConnection c) + throws LdapException { + org.apache.directory.api.ldap.model.message.SearchRequest s = new SearchRequestImpl(); + s.setFilter(new EqualityNode("uid", aOk)); + c.search(s); + } + + public void testApacheOk2(@RequestParam String aOk, LdapConnection c) + throws LdapException { + SearchRequestImpl s = new SearchRequestImpl(); + s.setFilter(new EqualityNode("uid", aOk)); + c.search(s); + } + + // ESAPI encoder sanitizer + public void testOk3(@RequestParam String okEncodeForLDAP, DirContext ctx) throws NamingException { + Encoder encoder = DefaultEncoder.getInstance(); + ctx.search("ou=system", "(uid=" + encoder.encodeForLDAP(okEncodeForLDAP) + ")", new SearchControls()); // False Positive + } + + // Spring LdapEncoder sanitizer + public void testOk4(@RequestParam String okFilterEncode, DirContext ctx) throws NamingException { + ctx.search("ou=system", "(uid=" + LdapEncoder.filterEncode(okFilterEncode) + ")", new SearchControls()); // False Positive + } + + // UnboundID Filter.encodeValue sanitizer + public void testOk5(@RequestParam String okUnboundEncodeValue, DirContext ctx) throws NamingException { + ctx.search("ou=system", "(uid=" + Filter.encodeValue(okUnboundEncodeValue) + ")", new SearchControls()); + } +} diff --git a/java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref b/java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref new file mode 100644 index 00000000000..2386248dc51 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref @@ -0,0 +1 @@ +Security/CWE/CWE-90/LdapInjection.ql diff --git a/java/ql/test/query-tests/security/CWE-090/options b/java/ql/test/query-tests/security/CWE-090/options new file mode 100644 index 00000000000..08c38d92ad8 --- /dev/null +++ b/java/ql/test/query-tests/security/CWE-090/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../stubs/springframework-5.2.3:${testdir}/../../../stubs/spring-ldap-2.3.2:${testdir}/../../../stubs/unboundid-ldap-4.0.14:${testdir}/../../../stubs/esapi-2.0.1:${testdir}/../../../stubs/apache-ldap-1.0.2 diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/EntryCursor.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/EntryCursor.java new file mode 100644 index 00000000000..fc2eecd4e50 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/EntryCursor.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.cursor; + +public interface EntryCursor { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/SearchCursor.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/SearchCursor.java new file mode 100644 index 00000000000..e27defdfde5 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/cursor/SearchCursor.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.cursor; + +public interface SearchCursor { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/entry/Value.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/entry/Value.java new file mode 100644 index 00000000000..b10f86cc626 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/entry/Value.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.entry; + +public interface Value { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapException.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapException.java new file mode 100644 index 00000000000..955a2446832 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapException.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.exception; + +public class LdapException extends Exception { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapInvalidDnException.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapInvalidDnException.java new file mode 100644 index 00000000000..8d44950a9b9 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/exception/LdapInvalidDnException.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.exception; + +public class LdapInvalidDnException extends LdapException { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/EqualityNode.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/EqualityNode.java new file mode 100644 index 00000000000..58c9d1981e9 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/EqualityNode.java @@ -0,0 +1,8 @@ +package org.apache.directory.api.ldap.model.filter; + +import org.apache.directory.api.ldap.model.entry.Value; + +public class EqualityNode implements ExprNode { + public EqualityNode(String attribute, Value value) { } + public EqualityNode(String attribute, String value) { } +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/ExprNode.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/ExprNode.java new file mode 100644 index 00000000000..8bb2e041159 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/filter/ExprNode.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.filter; + +public interface ExprNode { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequest.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequest.java new file mode 100644 index 00000000000..509785f8afc --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequest.java @@ -0,0 +1,12 @@ +package org.apache.directory.api.ldap.model.message; + +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.api.ldap.model.filter.ExprNode; + +public interface SearchRequest { + Dn getBase(); + SearchRequest setBase(Dn baseDn); + SearchRequest setFilter(ExprNode filter); + SearchRequest setFilter(String filter) throws LdapException; +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequestImpl.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequestImpl.java new file mode 100644 index 00000000000..ed0ffb4a607 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchRequestImpl.java @@ -0,0 +1,12 @@ +package org.apache.directory.api.ldap.model.message; + +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.name.Dn; +import org.apache.directory.api.ldap.model.filter.ExprNode; + +public class SearchRequestImpl implements SearchRequest { + public Dn getBase() { return null; } + public SearchRequest setBase(Dn baseDn) { return null; } + public SearchRequest setFilter(ExprNode filter) { return null; } + public SearchRequest setFilter(String filter) throws LdapException { return null; } +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchScope.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchScope.java new file mode 100644 index 00000000000..05e3138d7d3 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/message/SearchScope.java @@ -0,0 +1,4 @@ +package org.apache.directory.api.ldap.model.message; + +public enum SearchScope { +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/name/Dn.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/name/Dn.java new file mode 100644 index 00000000000..7f168474ef7 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/api/ldap/model/name/Dn.java @@ -0,0 +1,8 @@ +package org.apache.directory.api.ldap.model.name; + +import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException; + +public class Dn { + public Dn(String... upRdns) throws LdapInvalidDnException { } + public String getName() { return null; } +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapConnection.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapConnection.java new file mode 100644 index 00000000000..7891c621100 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapConnection.java @@ -0,0 +1,17 @@ +package org.apache.directory.ldap.client.api; + +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.cursor.EntryCursor; +import org.apache.directory.api.ldap.model.cursor.SearchCursor; +import org.apache.directory.api.ldap.model.message.SearchRequest; +import org.apache.directory.api.ldap.model.message.SearchScope; +import org.apache.directory.api.ldap.model.name.Dn; + +public interface LdapConnection { + SearchCursor search(SearchRequest searchRequest) throws LdapException; + + EntryCursor search(String baseDn, String filter, SearchScope scope, String... attributes) throws LdapException; + + EntryCursor search(Dn baseDn, String filter, SearchScope scope, String... attributes) throws LdapException; + +} diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapNetworkConnection.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapNetworkConnection.java new file mode 100644 index 00000000000..cc40586fc43 --- /dev/null +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapNetworkConnection.java @@ -0,0 +1,9 @@ +package org.apache.directory.ldap.client.api; + +import org.apache.directory.api.ldap.model.exception.LdapException; +import org.apache.directory.api.ldap.model.cursor.EntryCursor; +import org.apache.directory.api.ldap.model.message.SearchScope; + +public class LdapNetworkConnection /*implements LdapConnection*/ { + public EntryCursor search(String baseDn, String filter, SearchScope scope, String... attributes) throws LdapException { return null; } +} diff --git a/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/Encoder.java b/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/Encoder.java new file mode 100644 index 00000000000..e0200207e76 --- /dev/null +++ b/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/Encoder.java @@ -0,0 +1,5 @@ +package org.owasp.esapi; + +public interface Encoder { + String encodeForLDAP(String input); +} diff --git a/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java b/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java new file mode 100644 index 00000000000..9f93a3f67c5 --- /dev/null +++ b/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java @@ -0,0 +1,8 @@ +package org.owasp.esapi.reference; + +import org.owasp.esapi.Encoder; + +public class DefaultEncoder implements Encoder { + public static Encoder getInstance() { return null; } + public String encodeForLDAP(String input) { return input; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/ContextMapper.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/ContextMapper.java new file mode 100644 index 00000000000..951015b637e --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/ContextMapper.java @@ -0,0 +1,4 @@ +package org.springframework.ldap.core; + +public interface ContextMapper { +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/DirContextOperations.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/DirContextOperations.java new file mode 100644 index 00000000000..682de892a42 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/DirContextOperations.java @@ -0,0 +1,4 @@ +package org.springframework.ldap.core; + +public interface DirContextOperations { +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/LdapTemplate.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/LdapTemplate.java new file mode 100644 index 00000000000..2c26a3b3b50 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/LdapTemplate.java @@ -0,0 +1,28 @@ +package org.springframework.ldap.core; + +import java.util.*; + +import javax.naming.Name; +import javax.naming.directory.SearchControls; + +import org.springframework.ldap.filter.Filter; + +import org.springframework.ldap.query.LdapQuery; + +public class LdapTemplate { + public void authenticate(LdapQuery query, String password) { } + + public boolean authenticate(Name base, String filter, String password) { return true; } + + public List find(Name base, Filter filter, SearchControls searchControls, final Class clazz) { return null; } + + public List find(LdapQuery query, Class clazz) { return null; } + + public T findOne(LdapQuery query, Class clazz) { return null; } + + public void search(String base, String filter, int searchScope, boolean returningObjFlag, NameClassPairCallbackHandler handler) { } + + public DirContextOperations searchForContext(LdapQuery query) { return null; } + + public T searchForObject(Name base, String filter, ContextMapper mapper) { return null; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/NameClassPairCallbackHandler.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/NameClassPairCallbackHandler.java new file mode 100644 index 00000000000..250e6da0237 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/core/NameClassPairCallbackHandler.java @@ -0,0 +1,3 @@ +package org.springframework.ldap.core; + +public interface NameClassPairCallbackHandler { } diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/EqualsFilter.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/EqualsFilter.java new file mode 100644 index 00000000000..a5cbbd2a674 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/EqualsFilter.java @@ -0,0 +1,5 @@ +package org.springframework.ldap.filter; + +public class EqualsFilter implements Filter { + public EqualsFilter(String attribute, String value) { } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/Filter.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/Filter.java new file mode 100644 index 00000000000..b24091e6de0 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/Filter.java @@ -0,0 +1,4 @@ +package org.springframework.ldap.filter; + +public interface Filter { +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/HardcodedFilter.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/HardcodedFilter.java new file mode 100644 index 00000000000..b9cfa6dd5ce --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/HardcodedFilter.java @@ -0,0 +1,6 @@ +package org.springframework.ldap.filter; + +public class HardcodedFilter implements Filter { + public HardcodedFilter(String filter) { } + public StringBuffer encode(StringBuffer buff) { return buff; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ConditionCriteria.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ConditionCriteria.java new file mode 100644 index 00000000000..80cf59b6040 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ConditionCriteria.java @@ -0,0 +1,5 @@ +package org.springframework.ldap.query; + +public interface ConditionCriteria { + ContainerCriteria is(String value); +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ContainerCriteria.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ContainerCriteria.java new file mode 100644 index 00000000000..7a68b9fbab7 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/ContainerCriteria.java @@ -0,0 +1,4 @@ +package org.springframework.ldap.query; + +public interface ContainerCriteria extends LdapQuery { +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQuery.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQuery.java new file mode 100644 index 00000000000..c94bb75c20c --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQuery.java @@ -0,0 +1,4 @@ +package org.springframework.ldap.query; + +public interface LdapQuery { +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQueryBuilder.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQueryBuilder.java new file mode 100644 index 00000000000..2e6c76ccc55 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/query/LdapQueryBuilder.java @@ -0,0 +1,14 @@ +package org.springframework.ldap.query; + +import javax.naming.Name; +import org.springframework.ldap.filter.Filter; + +public class LdapQueryBuilder { + public static LdapQueryBuilder query() { return null; } + public LdapQuery filter(String hardcodedFilter) { return null; } + public LdapQuery filter(Filter filter) { return null; } + public LdapQuery filter(String filterFormat, Object... params) { return null; } + public LdapQueryBuilder base(String baseDn) { return this; } + public Name base() { return null; } + public ConditionCriteria where(String attribute) { return null; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapEncoder.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapEncoder.java new file mode 100644 index 00000000000..ea234029338 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapEncoder.java @@ -0,0 +1,5 @@ +package org.springframework.ldap.support; + +public class LdapEncoder { + public static String filterEncode(String value) { return value; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapNameBuilder.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapNameBuilder.java new file mode 100644 index 00000000000..74333407853 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapNameBuilder.java @@ -0,0 +1,12 @@ +package org.springframework.ldap.support; + +import javax.naming.ldap.LdapName; + +public class LdapNameBuilder { + public static LdapNameBuilder newInstance() { return null; } + public static LdapNameBuilder newInstance(String name) { return null; } + + public LdapNameBuilder add(String name) { return null; } + public LdapNameBuilder add(String key, Object value) { return null; } + public LdapName build() { return null; } +} diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapUtils.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapUtils.java new file mode 100644 index 00000000000..13fee96e004 --- /dev/null +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapUtils.java @@ -0,0 +1,7 @@ +package org.springframework.ldap.support; + +import javax.naming.ldap.LdapName; + +public class LdapUtils { + public static LdapName newLdapName(String distinguishedName) { return null; } +} diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java new file mode 100644 index 00000000000..5ae52ad123f --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/bind/annotation/RequestParam.java @@ -0,0 +1,8 @@ +package org.springframework.web.bind.annotation; + +import java.lang.annotation.*; + +@Target(value=ElementType.PARAMETER) +@Retention(value=RetentionPolicy.RUNTIME) +@Documented +public @interface RequestParam { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/AsyncRequestID.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/AsyncRequestID.java new file mode 100644 index 00000000000..09e9240e97e --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/AsyncRequestID.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class AsyncRequestID { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/DereferencePolicy.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/DereferencePolicy.java new file mode 100644 index 00000000000..76f554cf6da --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/DereferencePolicy.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class DereferencePolicy { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/Filter.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/Filter.java new file mode 100644 index 00000000000..2f24ed8c57a --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/Filter.java @@ -0,0 +1,13 @@ +package com.unboundid.ldap.sdk; + +public class Filter { + public static Filter create(java.lang.String filterString) throws LDAPException { return null; } + + public static Filter createNOTFilter(Filter notComponent) { return null; } + + public static Filter createEqualityFilter(java.lang.String attributeName, java.lang.String assertionValue) { return null; } + + public static java.lang.String encodeValue(java.lang.String value) { return null; } + + public void toNormalizedString(java.lang.StringBuilder buffer) { } +} diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPConnection.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPConnection.java new file mode 100644 index 00000000000..36ce082de57 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPConnection.java @@ -0,0 +1,21 @@ +package com.unboundid.ldap.sdk; + +public class LDAPConnection { + public AsyncRequestID asyncSearch(ReadOnlySearchRequest searchRequest) throws LDAPException { return null; } + public AsyncRequestID asyncSearch(SearchRequest searchRequest) throws LDAPException { return null; } + + public SearchResult search(ReadOnlySearchRequest searchRequest) throws LDAPSearchException { return null; } + public SearchResult search(SearchRequest searchRequest) throws LDAPSearchException { return null; } + + public SearchResult search(SearchResultListener searchResultListener, String baseDN, SearchScope scope, DereferencePolicy derefPolicy, + int sizeLimit, int timeLimit, boolean typesOnly, Filter filter, String... attributes) throws LDAPSearchException { return null; } + + public SearchResult search(SearchResultListener searchResultListener, String baseDN, SearchScope scope, DereferencePolicy derefPolicy, + int sizeLimit, int timeLimit, boolean typesOnly, String filter, String... attributes) throws LDAPSearchException { return null; } + + public SearchResult search(String baseDN, SearchScope scope, DereferencePolicy derefPolicy, int sizeLimit, int timeLimit, + boolean typesOnly, String filter, String... attributes) throws LDAPSearchException { return null; } + + public SearchResultEntry searchForEntry(String baseDN, SearchScope scope, DereferencePolicy derefPolicy, int timeLimit, + boolean typesOnly, String filter, String... attributes) throws LDAPSearchException { return null; } +} diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPException.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPException.java new file mode 100644 index 00000000000..b32b7e94b84 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPException.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class LDAPException extends Exception { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPSearchException.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPSearchException.java new file mode 100644 index 00000000000..29679929b31 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/LDAPSearchException.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class LDAPSearchException extends LDAPException { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/ReadOnlySearchRequest.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/ReadOnlySearchRequest.java new file mode 100644 index 00000000000..26c06ebfce6 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/ReadOnlySearchRequest.java @@ -0,0 +1,5 @@ +package com.unboundid.ldap.sdk; + +public interface ReadOnlySearchRequest { + SearchRequest duplicate(); +} diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchRequest.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchRequest.java new file mode 100644 index 00000000000..6c7156096b0 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchRequest.java @@ -0,0 +1,17 @@ +package com.unboundid.ldap.sdk; + +public class SearchRequest implements ReadOnlySearchRequest { + public SearchRequest(String baseDN, SearchScope scope, String filter, String... attributes) throws LDAPException { } + + public SearchRequest(SearchResultListener searchResultListener, String baseDN, SearchScope scope, DereferencePolicy derefPolicy, + int sizeLimit, int timeLimit, boolean typesOnly, Filter filter, String... attributes) { } + + public SearchRequest(SearchResultListener searchResultListener, String baseDN, SearchScope scope, DereferencePolicy derefPolicy, + int sizeLimit, int timeLimit, boolean typesOnly, String filter, String... attributes) throws LDAPException { } + + public SearchRequest duplicate() { return null; } + + public void setBaseDN(String baseDN) { } + + public void setFilter(String filter) throws LDAPException { } +} diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResult.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResult.java new file mode 100644 index 00000000000..98b7b07c4aa --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResult.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class SearchResult { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultEntry.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultEntry.java new file mode 100644 index 00000000000..637683f50af --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultEntry.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class SearchResultEntry { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultListener.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultListener.java new file mode 100644 index 00000000000..856793a91bf --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchResultListener.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public interface SearchResultListener { } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchScope.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchScope.java new file mode 100644 index 00000000000..18dda6314a8 --- /dev/null +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/SearchScope.java @@ -0,0 +1,3 @@ +package com.unboundid.ldap.sdk; + +public class SearchScope { } From b8834ffcad1ebe2cd0f69e1cb8bbd536d2c433c1 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 29 Jan 2020 13:06:45 +0100 Subject: [PATCH 077/148] add support for private fields in classes --- .../src/com/semmle/jcorn/Parser.java | 25 +++- .../src/com/semmle/jcorn/TokenType.java | 1 + .../semmle/javascript/dataflow/DataFlow.qll | 4 + .../test/library-tests/Classes/ClassFlow.qll | 17 +++ .../library-tests/Classes/PrivateField.qll | 9 ++ .../ql/test/library-tests/Classes/dataflow.js | 17 +++ .../library-tests/Classes/privateFields.js | 22 ++++ .../test/library-tests/Classes/tests.expected | 108 ++++++++++++++++++ .../ql/test/library-tests/Classes/tests.ql | 2 + 9 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 javascript/ql/test/library-tests/Classes/ClassFlow.qll create mode 100644 javascript/ql/test/library-tests/Classes/PrivateField.qll create mode 100644 javascript/ql/test/library-tests/Classes/dataflow.js create mode 100644 javascript/ql/test/library-tests/Classes/privateFields.js diff --git a/javascript/extractor/src/com/semmle/jcorn/Parser.java b/javascript/extractor/src/com/semmle/jcorn/Parser.java index f0e3c0d004f..7e344613b2e 100644 --- a/javascript/extractor/src/com/semmle/jcorn/Parser.java +++ b/javascript/extractor/src/com/semmle/jcorn/Parser.java @@ -5,6 +5,7 @@ import static com.semmle.jcorn.Whitespace.lineBreak; import com.semmle.jcorn.Identifiers.Dialect; import com.semmle.jcorn.Options.AllowReserved; +import com.semmle.jcorn.TokenType.Properties; import com.semmle.js.ast.ArrayExpression; import com.semmle.js.ast.ArrayPattern; import com.semmle.js.ast.ArrowFunctionExpression; @@ -44,6 +45,7 @@ import com.semmle.js.ast.INode; import com.semmle.js.ast.IPattern; import com.semmle.js.ast.Identifier; import com.semmle.js.ast.IfStatement; +import com.semmle.js.ast.FieldDefinition; import com.semmle.js.ast.ImportDeclaration; import com.semmle.js.ast.ImportDefaultSpecifier; import com.semmle.js.ast.ImportNamespaceSpecifier; @@ -124,6 +126,7 @@ public class Parser { private boolean inModule; protected boolean inFunction; protected boolean inGenerator; + protected boolean inClass; protected boolean inAsync; protected boolean inTemplateElement; protected int pos; @@ -240,8 +243,8 @@ public class Parser { // Used to signify the start of a potential arrow function this.potentialArrowAt = -1; - // Flags to track whether we are in a function, a generator, an async function. - this.inFunction = this.inGenerator = this.inAsync = false; + // Flags to track whether we are in a function, a generator, an async function, a class. + this.inFunction = this.inGenerator = this.inAsync = this.inClass = false; // Positions to delayed-check that yield/await does not exist in default parameters. this.yieldPos = this.awaitPos = 0; // Labels in scope. @@ -651,6 +654,9 @@ public class Parser { case 58: ++this.pos; return this.finishToken(TokenType.colon); + case 35: + ++this.pos; + return this.finishToken(TokenType.pound); case 63: return this.readToken_question(); @@ -2191,6 +2197,7 @@ public class Parser { // identifiers. protected Identifier parseIdent(boolean liberal) { Position startLoc = this.startLoc; + boolean isPrivateField = liberal && this.eat(TokenType.pound); if (liberal && this.options.allowReserved() == AllowReserved.NEVER) liberal = false; String name = null; if (this.type == TokenType.name) { @@ -2199,9 +2206,9 @@ public class Parser { && (this.options.ecmaVersion() >= 6 || inputSubstring(this.start, this.end).indexOf("\\") == -1)) this.raiseRecoverable(this.start, "The keyword '" + this.value + "' is reserved"); - if (this.inGenerator && this.value.equals("yield")) + if (!isPrivateField && this.inGenerator && this.value.equals("yield")) this.raiseRecoverable(this.start, "Can not use 'yield' as identifier inside a generator"); - if (this.inAsync && this.value.equals("await")) + if (!isPrivateField && this.inAsync && this.value.equals("await")) this.raiseRecoverable( this.start, "Can not use 'await' as identifier inside an async function"); name = String.valueOf(this.value); @@ -2213,6 +2220,12 @@ public class Parser { this.unexpected(); } this.next(); + if (isPrivateField) { + if (!this.inClass) { + this.raiseRecoverable(this.start, "Cannot use private fields outside a class"); + } + name = "#" + name; + } Identifier node = new Identifier(new SourceLocation(startLoc), name); return this.finishNode(node); } @@ -3127,6 +3140,8 @@ public class Parser { // Parse a class declaration or literal (depending on the // `isStatement` parameter). protected Node parseClass(Position startLoc, boolean isStatement) { + boolean oldInClass = this.inClass; + this.inClass = true; SourceLocation loc = new SourceLocation(startLoc); this.next(); Identifier id = this.parseClassId(isStatement); @@ -3145,6 +3160,8 @@ public class Parser { Node node; if (isStatement) node = new ClassDeclaration(loc, id, superClass, classBody); else node = new ClassExpression(loc, id, superClass, classBody); + + this.inClass = oldInClass; return this.finishNode(node); } diff --git a/javascript/extractor/src/com/semmle/jcorn/TokenType.java b/javascript/extractor/src/com/semmle/jcorn/TokenType.java index 25d6ee9cb53..e3c005c1b6a 100644 --- a/javascript/extractor/src/com/semmle/jcorn/TokenType.java +++ b/javascript/extractor/src/com/semmle/jcorn/TokenType.java @@ -85,6 +85,7 @@ public class TokenType { dot = new TokenType(new Properties(".")), questiondot = new TokenType(new Properties("?.")), question = new TokenType(new Properties("?").beforeExpr()), + pound = new TokenType(kw("#")), arrow = new TokenType(new Properties("=>").beforeExpr()), template = new TokenType(new Properties("template")), invalidTemplate = new TokenType(new Properties("invalidTemplate")), diff --git a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll index b8589c21393..aba2e3b5bb9 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll @@ -535,6 +535,10 @@ module DataFlow { */ pragma[noinline] predicate accesses(Node base, string p) { getBase() = base and getPropertyName() = p } + + predicate isPrivateField() { + getPropertyName().charAt(0) = "#" and getPropertyNameExpr() instanceof Label + } } /** diff --git a/javascript/ql/test/library-tests/Classes/ClassFlow.qll b/javascript/ql/test/library-tests/Classes/ClassFlow.qll new file mode 100644 index 00000000000..6a6d44b667f --- /dev/null +++ b/javascript/ql/test/library-tests/Classes/ClassFlow.qll @@ -0,0 +1,17 @@ +import javascript + +class Configuration extends DataFlow::Configuration { + Configuration() { this = "ClassDataFlowTestingConfig" } + + override predicate isSource(DataFlow::Node source) { + source.getEnclosingExpr().(StringLiteral).getValue().toLowerCase() = "source" + } + + override predicate isSink(DataFlow::Node sink) { + any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument() = sink + } +} + +query predicate dataflow(DataFlow::Node pred, DataFlow::Node succ) { + any(Configuration c).hasFlow(pred, succ) +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Classes/PrivateField.qll b/javascript/ql/test/library-tests/Classes/PrivateField.qll new file mode 100644 index 00000000000..a5b4a5ef04a --- /dev/null +++ b/javascript/ql/test/library-tests/Classes/PrivateField.qll @@ -0,0 +1,9 @@ +import javascript + +query string getAccessModifier(DataFlow::PropRef ref, Expr prop) { + prop = ref.getPropertyNameExpr() and + if ref.isPrivateField() then + result = "Private" + else + result = "Public" +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Classes/dataflow.js b/javascript/ql/test/library-tests/Classes/dataflow.js new file mode 100644 index 00000000000..bd4454f6b8e --- /dev/null +++ b/javascript/ql/test/library-tests/Classes/dataflow.js @@ -0,0 +1,17 @@ +(function () { + var source = "source"; + + class Foo { + #priv = source; + getPriv() { + return this.#priv; + } + + getFalsePrivate() { + return this["#priv"]; // not the same field as above. But we currently don't distinguish private and "public" fields. + } + } + sink(new Foo().getPriv()); // NOT OK. + + sink(new Foo().getFalsePrivate()); // OK [FP: Is FP because we do nothing special about dataflow for private fields.] +})(); \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Classes/privateFields.js b/javascript/ql/test/library-tests/Classes/privateFields.js new file mode 100644 index 00000000000..169482e5826 --- /dev/null +++ b/javascript/ql/test/library-tests/Classes/privateFields.js @@ -0,0 +1,22 @@ +class Foo { + #privDecl = 3; + #if = "if"; // "keywords" are ok. + reads() { + var foo = this.#privUse; + var bar = this["#publicComputed"] + var baz = this.#if; + } + + equals(o) { + return this.#privDecl === o.#privDecl; + } + + writes() { + this.#privDecl = 4; + this["#public"] = 5; + } + + #privSecond; // is a PropNode, not a PropRef. Doesn't matter. + + ["#publicField"] = 6; +} \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Classes/tests.expected b/javascript/ql/test/library-tests/Classes/tests.expected index 3673235a6c2..664a82e4422 100644 --- a/javascript/ql/test/library-tests/Classes/tests.expected +++ b/javascript/ql/test/library-tests/Classes/tests.expected @@ -1,5 +1,9 @@ test_FieldInits +| dataflow.js:5:3:5:17 | #priv = source; | dataflow.js:5:11:5:16 | source | | fields.js:3:3:3:8 | y = 42 | fields.js:3:7:3:8 | 42 | +| privateFields.js:2:2:2:15 | #privDecl = 3; | privateFields.js:2:14:2:14 | 3 | +| privateFields.js:3:2:3:12 | #if = "if"; | privateFields.js:3:8:3:11 | "if" | +| privateFields.js:21:2:21:22 | ["#publ ... "] = 6; | privateFields.js:21:21:21:21 | 6 | test_ComputedMethods | tst.js:3:3:3:56 | ["const ... r. */ } | | tst.js:13:3:13:10 | [m]() {} | @@ -15,9 +19,11 @@ test_ClassNodeStaticMethod | points.js:20:1:33:1 | class C ... ;\\n }\\n} | className | points.js:30:19:32:3 | () {\\n ... t";\\n } | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | constructor | staticConstructor.js:2:21:2:59 | () { re ... tor"; } | test_ClassDefinitions +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | | fields.js:1:1:4:1 | class C ... = 42\\n} | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | | tst.js:1:9:4:1 | class { ... */ }\\n} | | tst.js:6:1:8:1 | class B ... t); }\\n} | @@ -25,17 +31,27 @@ test_ClassDefinitions test_AccessorMethods | points.js:7:3:9:3 | get dis ... y);\\n } | test_Fields +| dataflow.js:5:3:5:17 | #priv = source; | dataflow.js:5:3:5:7 | #priv | | fields.js:2:3:2:4 | x; | fields.js:2:3:2:3 | x | | fields.js:3:3:3:8 | y = 42 | fields.js:3:3:3:3 | y | +| privateFields.js:2:2:2:15 | #privDecl = 3; | privateFields.js:2:2:2:10 | #privDecl | +| privateFields.js:3:2:3:12 | #if = "if"; | privateFields.js:3:2:3:4 | #if | +| privateFields.js:19:2:19:13 | #privSecond; | privateFields.js:19:2:19:12 | #privSecond | +| privateFields.js:21:2:21:22 | ["#publ ... "] = 6; | privateFields.js:21:3:21:16 | "#publicField" | test_ClassDefinition_getName +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | Foo | | fields.js:1:1:4:1 | class C ... = 42\\n} | C | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | Point | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | ColouredPoint | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | Foo | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | MyClass | | tst.js:1:9:4:1 | class { ... */ }\\n} | A | | tst.js:6:1:8:1 | class B ... t); }\\n} | B | | tst.js:11:1:14:1 | class C ... () {}\\n} | C | test_MethodDefinitions +| dataflow.js:4:12:4:11 | constructor() {} | dataflow.js:4:12:4:11 | constructor | dataflow.js:4:12:4:11 | () {} | dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | +| dataflow.js:6:3:8:3 | getPriv ... iv;\\n\\t\\t} | dataflow.js:6:3:6:9 | getPriv | dataflow.js:6:10:8:3 | () {\\n\\t\\t ... iv;\\n\\t\\t} | dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | +| dataflow.js:10:3:12:3 | getFals ... . \\n\\t\\t} | dataflow.js:10:3:10:17 | getFalsePrivate | dataflow.js:10:18:12:3 | () {\\n\\t\\t ... . \\n\\t\\t} | dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | | fields.js:1:9:1:8 | constructor() {} | fields.js:1:9:1:8 | constructor | fields.js:1:9:1:8 | () {} | fields.js:1:1:4:1 | class C ... = 42\\n} | | points.js:2:3:5:3 | constru ... y;\\n } | points.js:2:3:2:13 | constructor | points.js:2:14:5:3 | (x, y) ... y;\\n } | points.js:1:1:18:1 | class P ... ;\\n }\\n} | | points.js:7:3:9:3 | get dis ... y);\\n } | points.js:7:7:7:10 | dist | points.js:7:11:9:3 | () {\\n ... y);\\n } | points.js:1:1:18:1 | class P ... ;\\n }\\n} | @@ -44,6 +60,10 @@ test_MethodDefinitions | points.js:21:3:24:3 | constru ... c;\\n } | points.js:21:3:21:13 | constructor | points.js:21:14:24:3 | (x, y, ... c;\\n } | points.js:20:1:33:1 | class C ... ;\\n }\\n} | | points.js:26:3:28:3 | toStrin ... ur;\\n } | points.js:26:3:26:10 | toString | points.js:26:11:28:3 | () {\\n ... ur;\\n } | points.js:20:1:33:1 | class C ... ;\\n }\\n} | | points.js:30:3:32:3 | static ... t";\\n } | points.js:30:10:30:18 | className | points.js:30:19:32:3 | () {\\n ... t";\\n } | points.js:20:1:33:1 | class C ... ;\\n }\\n} | +| privateFields.js:1:11:1:10 | constructor() {} | privateFields.js:1:11:1:10 | constructor | privateFields.js:1:11:1:10 | () {} | privateFields.js:1:1:22:1 | class F ... = 6;\\n} | +| privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | privateFields.js:4:2:4:6 | reads | privateFields.js:4:7:8:2 | () {\\n\\t\\t ... #if;\\n\\t} | privateFields.js:1:1:22:1 | class F ... = 6;\\n} | +| privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | privateFields.js:10:2:10:7 | equals | privateFields.js:10:8:12:2 | (o) {\\n\\t ... ecl;\\n\\t} | privateFields.js:1:1:22:1 | class F ... = 6;\\n} | +| privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | privateFields.js:14:2:14:7 | writes | privateFields.js:14:8:17:2 | () {\\n\\t\\t ... = 5;\\n\\t} | privateFields.js:1:1:22:1 | class F ... = 6;\\n} | | staticConstructor.js:1:15:1:14 | constructor() {} | staticConstructor.js:1:15:1:14 | constructor | staticConstructor.js:1:15:1:14 | () {} | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | | staticConstructor.js:2:3:2:59 | static ... tor"; } | staticConstructor.js:2:10:2:20 | constructor | staticConstructor.js:2:21:2:59 | () { re ... tor"; } | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | | tst.js:2:3:2:50 | "constr ... r. */ } | tst.js:2:3:2:15 | "constructor" | tst.js:2:16:2:50 | () { /* ... r. */ } | tst.js:1:9:4:1 | class { ... */ }\\n} | @@ -53,6 +73,10 @@ test_MethodDefinitions | tst.js:12:3:12:8 | m() {} | tst.js:12:3:12:3 | m | tst.js:12:4:12:8 | () {} | tst.js:11:1:14:1 | class C ... () {}\\n} | | tst.js:13:3:13:10 | [m]() {} | tst.js:13:4:13:4 | m | tst.js:13:6:13:10 | () {} | tst.js:11:1:14:1 | class C ... () {}\\n} | test_getAMember +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:4:12:4:11 | constructor() {} | +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:5:3:5:17 | #priv = source; | +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:6:3:8:3 | getPriv ... iv;\\n\\t\\t} | +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:10:3:12:3 | getFals ... . \\n\\t\\t} | | fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:1:9:1:8 | constructor() {} | | fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:2:3:2:4 | x; | | fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:3:3:3:8 | y = 42 | @@ -63,6 +87,14 @@ test_getAMember | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:21:3:24:3 | constru ... c;\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:26:3:28:3 | toStrin ... ur;\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:30:3:32:3 | static ... t";\\n } | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:1:11:1:10 | constructor() {} | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:2:2:2:15 | #privDecl = 3; | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:3:2:3:12 | #if = "if"; | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:19:2:19:13 | #privSecond; | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:21:2:21:22 | ["#publ ... "] = 6; | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | staticConstructor.js:1:15:1:14 | constructor() {} | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | staticConstructor.js:2:3:2:59 | static ... tor"; } | | tst.js:1:9:4:1 | class { ... */ }\\n} | tst.js:2:3:2:50 | "constr ... r. */ } | @@ -72,6 +104,9 @@ test_getAMember | tst.js:11:1:14:1 | class C ... () {}\\n} | tst.js:12:3:12:8 | m() {} | | tst.js:11:1:14:1 | class C ... () {}\\n} | tst.js:13:3:13:10 | [m]() {} | test_MethodNames +| dataflow.js:4:12:4:11 | constructor() {} | constructor | +| dataflow.js:6:3:8:3 | getPriv ... iv;\\n\\t\\t} | getPriv | +| dataflow.js:10:3:12:3 | getFals ... . \\n\\t\\t} | getFalsePrivate | | fields.js:1:9:1:8 | constructor() {} | constructor | | points.js:2:3:5:3 | constru ... y;\\n } | constructor | | points.js:7:3:9:3 | get dis ... y);\\n } | dist | @@ -80,6 +115,10 @@ test_MethodNames | points.js:21:3:24:3 | constru ... c;\\n } | constructor | | points.js:26:3:28:3 | toStrin ... ur;\\n } | toString | | points.js:30:3:32:3 | static ... t";\\n } | className | +| privateFields.js:1:11:1:10 | constructor() {} | constructor | +| privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | reads | +| privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | equals | +| privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | writes | | staticConstructor.js:1:15:1:14 | constructor() {} | constructor | | staticConstructor.js:2:3:2:59 | static ... tor"; } | constructor | | tst.js:2:3:2:50 | "constr ... r. */ } | constructor | @@ -94,27 +133,96 @@ test_SuperExpr | points.js:27:12:27:16 | super | | tst.js:7:19:7:23 | super | test_SyntheticConstructors +| dataflow.js:4:12:4:11 | constructor() {} | | fields.js:1:9:1:8 | constructor() {} | +| privateFields.js:1:11:1:10 | constructor() {} | | staticConstructor.js:1:15:1:14 | constructor() {} | | tst.js:11:9:11:8 | constructor() {} | test_ConstructorDefinitions +| dataflow.js:4:12:4:11 | constructor() {} | | fields.js:1:9:1:8 | constructor() {} | | points.js:2:3:5:3 | constru ... y;\\n } | | points.js:21:3:24:3 | constru ... c;\\n } | +| privateFields.js:1:11:1:10 | constructor() {} | | staticConstructor.js:1:15:1:14 | constructor() {} | | tst.js:2:3:2:50 | "constr ... r. */ } | | tst.js:7:3:7:38 | constru ... get); } | | tst.js:11:9:11:8 | constructor() {} | test_ClassNodeConstructor +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | dataflow.js:4:12:4:11 | () {} | | fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:1:9:1:8 | () {} | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | points.js:2:14:5:3 | (x, y) ... y;\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:21:14:24:3 | (x, y, ... c;\\n } | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:1:11:1:10 | () {} | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | staticConstructor.js:1:15:1:14 | () {} | | tst.js:1:9:4:1 | class { ... */ }\\n} | tst.js:2:16:2:50 | () { /* ... r. */ } | | tst.js:6:1:8:1 | class B ... t); }\\n} | tst.js:7:14:7:38 | () { su ... get); } | | tst.js:11:1:14:1 | class C ... () {}\\n} | tst.js:11:9:11:8 | () {} | test_ClassNodeInstanceMethod +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | getFalsePrivate | dataflow.js:10:18:12:3 | () {\\n\\t\\t ... . \\n\\t\\t} | +| dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | getPriv | dataflow.js:6:10:8:3 | () {\\n\\t\\t ... iv;\\n\\t\\t} | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | toString | points.js:11:11:13:3 | () {\\n ... )";\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | toString | points.js:26:11:28:3 | () {\\n ... ur;\\n } | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | equals | privateFields.js:10:8:12:2 | (o) {\\n\\t ... ecl;\\n\\t} | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | reads | privateFields.js:4:7:8:2 | () {\\n\\t\\t ... #if;\\n\\t} | +| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | writes | privateFields.js:14:8:17:2 | () {\\n\\t\\t ... = 5;\\n\\t} | | tst.js:1:9:4:1 | class { ... */ }\\n} | constructor | tst.js:3:18:3:56 | () { /* ... r. */ } | | tst.js:11:1:14:1 | class C ... () {}\\n} | m | tst.js:12:4:12:8 | () {} | +getAccessModifier +| dataflow.js:4:12:4:11 | constructor() {} | dataflow.js:4:12:4:11 | constructor | Public | +| dataflow.js:5:3:5:17 | #priv = source; | dataflow.js:5:3:5:7 | #priv | Private | +| dataflow.js:6:3:8:3 | getPriv ... iv;\\n\\t\\t} | dataflow.js:6:3:6:9 | getPriv | Public | +| dataflow.js:7:11:7:20 | this.#priv | dataflow.js:7:16:7:20 | #priv | Private | +| dataflow.js:10:3:12:3 | getFals ... . \\n\\t\\t} | dataflow.js:10:3:10:17 | getFalsePrivate | Public | +| dataflow.js:11:11:11:23 | this["#priv"] | dataflow.js:11:16:11:22 | "#priv" | Public | +| dataflow.js:14:7:14:23 | new Foo().getPriv | dataflow.js:14:17:14:23 | getPriv | Public | +| dataflow.js:16:7:16:31 | new Foo ... Private | dataflow.js:16:17:16:31 | getFalsePrivate | Public | +| fields.js:1:9:1:8 | constructor() {} | fields.js:1:9:1:8 | constructor | Public | +| fields.js:3:3:3:8 | y = 42 | fields.js:3:3:3:3 | y | Public | +| points.js:2:3:5:3 | constru ... y;\\n } | points.js:2:3:2:13 | constructor | Public | +| points.js:3:5:3:10 | this.x | points.js:3:10:3:10 | x | Public | +| points.js:4:5:4:10 | this.y | points.js:4:10:4:10 | y | Public | +| points.js:7:3:9:3 | get dis ... y);\\n } | points.js:7:7:7:10 | dist | Public | +| points.js:8:12:8:20 | Math.sqrt | points.js:8:17:8:20 | sqrt | Public | +| points.js:8:22:8:27 | this.x | points.js:8:27:8:27 | x | Public | +| points.js:8:29:8:34 | this.x | points.js:8:34:8:34 | x | Public | +| points.js:8:36:8:41 | this.y | points.js:8:41:8:41 | y | Public | +| points.js:8:43:8:48 | this.y | points.js:8:48:8:48 | y | Public | +| points.js:11:3:13:3 | toStrin ... )";\\n } | points.js:11:3:11:10 | toString | Public | +| points.js:12:18:12:23 | this.x | points.js:12:23:12:23 | x | Public | +| points.js:12:34:12:39 | this.y | points.js:12:39:12:39 | y | Public | +| points.js:15:3:17:3 | static ... t";\\n } | points.js:15:10:15:18 | className | Public | +| points.js:21:3:24:3 | constru ... c;\\n } | points.js:21:3:21:13 | constructor | Public | +| points.js:23:5:23:15 | this.colour | points.js:23:10:23:15 | colour | Public | +| points.js:26:3:28:3 | toStrin ... ur;\\n } | points.js:26:3:26:10 | toString | Public | +| points.js:27:12:27:25 | super.toString | points.js:27:18:27:25 | toString | Public | +| points.js:27:40:27:50 | this.colour | points.js:27:45:27:50 | colour | Public | +| points.js:30:3:32:3 | static ... t";\\n } | points.js:30:10:30:18 | className | Public | +| privateFields.js:1:11:1:10 | constructor() {} | privateFields.js:1:11:1:10 | constructor | Public | +| privateFields.js:2:2:2:15 | #privDecl = 3; | privateFields.js:2:2:2:10 | #privDecl | Private | +| privateFields.js:3:2:3:12 | #if = "if"; | privateFields.js:3:2:3:4 | #if | Private | +| privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | privateFields.js:4:2:4:6 | reads | Public | +| privateFields.js:5:13:5:25 | this.#privUse | privateFields.js:5:18:5:25 | #privUse | Private | +| privateFields.js:6:13:6:35 | this["# ... puted"] | privateFields.js:6:18:6:34 | "#publicComputed" | Public | +| privateFields.js:7:13:7:20 | this.#if | privateFields.js:7:18:7:20 | #if | Private | +| privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | privateFields.js:10:2:10:7 | equals | Public | +| privateFields.js:11:10:11:23 | this.#privDecl | privateFields.js:11:15:11:23 | #privDecl | Private | +| privateFields.js:11:29:11:39 | o.#privDecl | privateFields.js:11:31:11:39 | #privDecl | Private | +| privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | privateFields.js:14:2:14:7 | writes | Public | +| privateFields.js:15:3:15:16 | this.#privDecl | privateFields.js:15:8:15:16 | #privDecl | Private | +| privateFields.js:16:3:16:17 | this["#public"] | privateFields.js:16:8:16:16 | "#public" | Public | +| privateFields.js:21:2:21:22 | ["#publ ... "] = 6; | privateFields.js:21:3:21:16 | "#publicField" | Public | +| staticConstructor.js:1:15:1:14 | constructor() {} | staticConstructor.js:1:15:1:14 | constructor | Public | +| staticConstructor.js:2:3:2:59 | static ... tor"; } | staticConstructor.js:2:10:2:20 | constructor | Public | +| staticConstructor.js:4:1:4:11 | console.log | staticConstructor.js:4:9:4:11 | log | Public | +| staticConstructor.js:4:13:4:31 | MyClass.constructor | staticConstructor.js:4:21:4:31 | constructor | Public | +| tst.js:2:3:2:50 | "constr ... r. */ } | tst.js:2:3:2:15 | "constructor" | Public | +| tst.js:3:3:3:56 | ["const ... r. */ } | tst.js:3:4:3:16 | "constructor" | Public | +| tst.js:7:3:7:38 | constru ... get); } | tst.js:7:3:7:13 | constructor | Public | +| tst.js:11:9:11:8 | constructor() {} | tst.js:11:9:11:8 | constructor | Public | +| tst.js:12:3:12:8 | m() {} | tst.js:12:3:12:3 | m | Public | +| tst.js:13:3:13:10 | [m]() {} | tst.js:13:4:13:4 | m | Public | +| tst.js:17:3:17:20 | m() { return 42; } | tst.js:17:3:17:3 | m | Public | +dataflow +| dataflow.js:2:15:2:22 | "source" | dataflow.js:14:7:14:25 | new Foo().getPriv() | +| dataflow.js:2:15:2:22 | "source" | dataflow.js:16:7:16:33 | new Foo ... ivate() | diff --git a/javascript/ql/test/library-tests/Classes/tests.ql b/javascript/ql/test/library-tests/Classes/tests.ql index 3009546636c..41fb13f3af0 100644 --- a/javascript/ql/test/library-tests/Classes/tests.ql +++ b/javascript/ql/test/library-tests/Classes/tests.ql @@ -16,3 +16,5 @@ import SyntheticConstructors import ConstructorDefinitions import ClassNodeConstructor import ClassNodeInstanceMethod +import PrivateField +import ClassFlow \ No newline at end of file From d4d910b68127f5182297cd7c0c0a1940b4a7cc9f Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Wed, 29 Jan 2020 14:37:40 +0100 Subject: [PATCH 078/148] JS: add koa test --- javascript/ql/test/library-tests/frameworks/koa/src/koa.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/javascript/ql/test/library-tests/frameworks/koa/src/koa.js b/javascript/ql/test/library-tests/frameworks/koa/src/koa.js index 24680a4a746..479298fd405 100644 --- a/javascript/ql/test/library-tests/frameworks/koa/src/koa.js +++ b/javascript/ql/test/library-tests/frameworks/koa/src/koa.js @@ -54,3 +54,8 @@ app2.use(async ctx => { var headers = ctx.headers; headers.foo; }); + +var app3 = Koa(); +app3.use(function*(){ + this.request.url; +}); From a6d3afd81797afc0ea906d6d1b0c05cbbc6f72a0 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Wed, 29 Jan 2020 14:42:17 +0100 Subject: [PATCH 079/148] JS: support additional Koa request sources --- change-notes/1.24/analysis-javascript.md | 1 + .../src/semmle/javascript/frameworks/Koa.qll | 22 ++++++++++++++++++- .../frameworks/koa/tests.expected | 10 +++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index d367d235a8c..21411b01179 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -18,6 +18,7 @@ - [Socket.IO](https://socket.io/) - [ws](https://github.com/websockets/ws) - [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) + - [Koa](https://www.npmjs.com/package/koa) ## New queries diff --git a/javascript/ql/src/semmle/javascript/frameworks/Koa.qll b/javascript/ql/src/semmle/javascript/frameworks/Koa.qll index ca38db8429e..bcc47bad9eb 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Koa.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Koa.qll @@ -9,7 +9,7 @@ module Koa { /** * An expression that creates a new Koa application. */ - class AppDefinition extends HTTP::Servers::StandardServerDefinition, NewExpr { + class AppDefinition extends HTTP::Servers::StandardServerDefinition, InvokeExpr { AppDefinition() { // `app = new Koa()` this = DataFlow::moduleImport("koa").getAnInvocation().asExpr() @@ -115,6 +115,26 @@ module Koa { override RouteHandler getRouteHandler() { result = ctx.getRouteHandler() } } + /** + * A Koa request source, accessed through the a request property of a + * generator route handler (deprecated in Koa 3). + */ + private class GeneratorRequestSource extends HTTP::Servers::RequestSource { + RouteHandler rh; + + GeneratorRequestSource() { + exists(DataFlow::FunctionNode fun | fun = rh | + fun.getFunction().isGenerator() and + fun.getReceiver().getAPropertyRead("request") = this + ) + } + + /** + * Gets the route handler that provides this response. + */ + override RouteHandler getRouteHandler() { result = rh } + } + /** * A Koa response source, that is, an access to the `response` property * of a context object. diff --git a/javascript/ql/test/library-tests/frameworks/koa/tests.expected b/javascript/ql/test/library-tests/frameworks/koa/tests.expected index 0d8e37d13a6..8e6f1aa06a2 100644 --- a/javascript/ql/test/library-tests/frameworks/koa/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/koa/tests.expected @@ -3,6 +3,7 @@ test_RouteSetup | src/koa.js:10:1:28:2 | app2.us ... z');\\n}) | | src/koa.js:30:1:45:2 | app2.us ... rl);\\n}) | | src/koa.js:47:1:56:2 | app2.us ... foo;\\n}) | +| src/koa.js:59:1:61:2 | app3.us ... url;\\n}) | test_RequestInputAccess | src/koa.js:19:3:19:18 | ctx.request.body | body | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:20:3:20:23 | ctx.req ... ery.foo | parameter | src/koa.js:10:10:28:1 | functio ... az');\\n} | @@ -24,6 +25,7 @@ test_RequestInputAccess | src/koa.js:49:2:49:14 | cookies.get() | cookie | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | | src/koa.js:52:2:52:10 | query.foo | parameter | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | | src/koa.js:55:2:55:12 | headers.foo | header | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | +| src/koa.js:60:2:60:17 | this.request.url | url | src/koa.js:59:10:61:1 | functio ... .url;\\n} | test_RouteHandler_getAResponseHeader | src/koa.js:10:10:28:1 | functio ... az');\\n} | header1 | src/koa.js:11:3:11:25 | this.se ... 1', '') | | src/koa.js:10:10:28:1 | functio ... az');\\n} | header2 | src/koa.js:12:3:12:37 | this.re ... 2', '') | @@ -75,6 +77,7 @@ test_RouteHandler_getAContextExpr | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:48:16:48:18 | ctx | | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:51:14:51:16 | ctx | | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:54:16:54:18 | ctx | +| src/koa.js:59:10:61:1 | functio ... .url;\\n} | src/koa.js:60:2:60:5 | this | test_HeaderDefinition | src/koa.js:11:3:11:25 | this.se ... 1', '') | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:12:3:12:37 | this.re ... 2', '') | src/koa.js:10:10:28:1 | functio ... az');\\n} | @@ -87,6 +90,7 @@ test_RouteSetup_getServer | src/koa.js:10:1:28:2 | app2.us ... z');\\n}) | src/koa.js:5:12:5:20 | new Koa() | | src/koa.js:30:1:45:2 | app2.us ... rl);\\n}) | src/koa.js:5:12:5:20 | new Koa() | | src/koa.js:47:1:56:2 | app2.us ... foo;\\n}) | src/koa.js:5:12:5:20 | new Koa() | +| src/koa.js:59:1:61:2 | app3.us ... url;\\n}) | src/koa.js:58:12:58:16 | Koa() | test_HeaderDefinition_getAHeaderName | src/koa.js:11:3:11:25 | this.se ... 1', '') | header1 | | src/koa.js:12:3:12:37 | this.re ... 2', '') | header2 | @@ -116,14 +120,17 @@ test_RouteSetup_getARouteHandler | src/koa.js:10:1:28:2 | app2.us ... z');\\n}) | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:30:1:45:2 | app2.us ... rl);\\n}) | src/koa.js:30:10:45:1 | async c ... url);\\n} | | src/koa.js:47:1:56:2 | app2.us ... foo;\\n}) | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | +| src/koa.js:59:1:61:2 | app3.us ... url;\\n}) | src/koa.js:59:10:61:1 | functio ... .url;\\n} | test_AppDefinition | src/koa.js:2:12:2:33 | new (re ... oa'))() | | src/koa.js:5:12:5:20 | new Koa() | +| src/koa.js:58:12:58:16 | Koa() | test_RouteHandler | src/koa.js:7:1:7:22 | functio ... r1() {} | src/koa.js:5:12:5:20 | new Koa() | | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:5:12:5:20 | new Koa() | | src/koa.js:30:10:45:1 | async c ... url);\\n} | src/koa.js:5:12:5:20 | new Koa() | | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | src/koa.js:5:12:5:20 | new Koa() | +| src/koa.js:59:10:61:1 | functio ... .url;\\n} | src/koa.js:58:12:58:16 | Koa() | test_RequestExpr | src/koa.js:19:3:19:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:20:3:20:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} | @@ -133,6 +140,7 @@ test_RequestExpr | src/koa.js:24:3:24:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:25:3:25:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:26:3:26:13 | ctx.request | src/koa.js:10:10:28:1 | functio ... az');\\n} | +| src/koa.js:60:2:60:13 | this.request | src/koa.js:59:10:61:1 | functio ... .url;\\n} | test_RouteHandler_getARequestExpr | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:19:3:19:13 | ctx.request | | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:20:3:20:13 | ctx.request | @@ -142,6 +150,7 @@ test_RouteHandler_getARequestExpr | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:24:3:24:13 | ctx.request | | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:25:3:25:13 | ctx.request | | src/koa.js:10:10:28:1 | functio ... az');\\n} | src/koa.js:26:3:26:13 | ctx.request | +| src/koa.js:59:10:61:1 | functio ... .url;\\n} | src/koa.js:60:2:60:13 | this.request | test_ContextExpr | src/koa.js:11:3:11:6 | this | src/koa.js:10:10:28:1 | functio ... az');\\n} | | src/koa.js:12:3:12:6 | this | src/koa.js:10:10:28:1 | functio ... az');\\n} | @@ -174,6 +183,7 @@ test_ContextExpr | src/koa.js:48:16:48:18 | ctx | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | | src/koa.js:51:14:51:16 | ctx | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | | src/koa.js:54:16:54:18 | ctx | src/koa.js:47:10:56:1 | async c ... .foo;\\n} | +| src/koa.js:60:2:60:5 | this | src/koa.js:59:10:61:1 | functio ... .url;\\n} | test_RedirectInvocation | src/koa.js:43:2:43:18 | ctx.redirect(url) | src/koa.js:43:15:43:17 | url | src/koa.js:30:10:45:1 | async c ... url);\\n} | | src/koa.js:44:2:44:27 | ctx.res ... ct(url) | src/koa.js:44:24:44:26 | url | src/koa.js:30:10:45:1 | async c ... url);\\n} | From 9b651ea92cc4b682a1e246d60836a144e28f0d80 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Wed, 29 Jan 2020 15:02:46 +0100 Subject: [PATCH 080/148] C++: Fix mapping of sources from Expr to Node The code contained the remains of how `isUserInput` in `Security.qll` used to be ported to IR. It's wrong to use that port since many queries call `userInput` directly to get the "cause" string. --- .../cpp/ir/dataflow/DefaultTaintTracking.qll | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll index 5005d694a15..8dee7a30e2b 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -21,31 +21,19 @@ private predicate predictableInstruction(Instruction instr) { predictableInstruction(instr.(UnaryInstruction).getUnary()) } +private DataFlow::Node getNodeForSource(Expr source) { + isUserInput(source, _) and + ( + result = DataFlow::exprNode(source) + or + result = DataFlow::definitionByReferenceNode(source) + ) +} + private class DefaultTaintTrackingCfg extends DataFlow::Configuration { DefaultTaintTrackingCfg() { this = "DefaultTaintTrackingCfg" } - override predicate isSource(DataFlow::Node source) { - exists(CallInstruction ci, WriteSideEffectInstruction wsei | - userInputArgument(ci.getConvertedResultExpression(), wsei.getIndex()) and - source.asInstruction() = wsei and - wsei.getPrimaryInstruction() = ci - ) - or - userInputReturned(source.asExpr()) - or - isUserInput(source.asExpr(), _) - or - source.asExpr() instanceof EnvironmentRead - or - source - .asInstruction() - .(LoadInstruction) - .getSourceAddress() - .(VariableAddressInstruction) - .getASTVariable() - .hasName("argv") and - source.asInstruction().getEnclosingFunction().hasGlobalName("main") - } + override predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) } override predicate isSink(DataFlow::Node sink) { any() } @@ -59,7 +47,7 @@ private class DefaultTaintTrackingCfg extends DataFlow::Configuration { private class ToGlobalVarTaintTrackingCfg extends DataFlow::Configuration { ToGlobalVarTaintTrackingCfg() { this = "GlobalVarTaintTrackingCfg" } - override predicate isSource(DataFlow::Node source) { isUserInput(source.asExpr(), _) } + override predicate isSource(DataFlow::Node source) { source = getNodeForSource(_) } override predicate isSink(DataFlow::Node sink) { exists(GlobalOrNamespaceVariable gv | writesVariable(sink.asInstruction(), gv)) @@ -283,10 +271,7 @@ private Element adjustedSink(DataFlow::Node sink) { predicate tainted(Expr source, Element tainted) { exists(DefaultTaintTrackingCfg cfg, DataFlow::Node sink | - cfg.hasFlow(DataFlow::exprNode(source), sink) - or - cfg.hasFlow(DataFlow::definitionByReferenceNode(source), sink) - | + cfg.hasFlow(getNodeForSource(source), sink) and tainted = adjustedSink(sink) ) } @@ -299,7 +284,7 @@ predicate taintedIncludingGlobalVars(Expr source, Element tainted, string global ToGlobalVarTaintTrackingCfg toCfg, FromGlobalVarTaintTrackingCfg fromCfg, DataFlow::Node store, GlobalOrNamespaceVariable global, DataFlow::Node load, DataFlow::Node sink | - toCfg.hasFlow(DataFlow::exprNode(source), store) and + toCfg.hasFlow(getNodeForSource(source), store) and store .asInstruction() .(StoreInstruction) From 2039ec37e5d40d2c3c0af38b6ea54d91f96a96fc Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 29 Jan 2020 16:26:23 +0100 Subject: [PATCH 081/148] Java/C++/C#: Add change note for taint-getters. --- change-notes/1.24/analysis-cpp.md | 4 ++++ change-notes/1.24/analysis-csharp.md | 4 ++++ change-notes/1.24/analysis-java.md | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/change-notes/1.24/analysis-cpp.md b/change-notes/1.24/analysis-cpp.md index aa4bbdf5887..94c84e991a5 100644 --- a/change-notes/1.24/analysis-cpp.md +++ b/change-notes/1.24/analysis-cpp.md @@ -26,6 +26,10 @@ The following changes in version 1.24 affect C/C++ analysis in all applications. ## Changes to libraries +* The data-flow library has been improved when flow through functions needs to be + combined with both taint tracking and flow through fields allowing more flow + to be tracked. This affects and improves all security queries, which may + report additional results. * Created the `semmle.code.cpp.models.interfaces.Allocation` library to model allocation such as `new` expressions and calls to `malloc`. This in intended to replace the functionality in `semmle.code.cpp.commons.Alloc` with a more consistent and useful interface. * Created the `semmle.code.cpp.models.interfaces.Deallocation` library to model deallocation such as `delete` expressions and calls to `free`. This in intended to replace the functionality in `semmle.code.cpp.commons.Alloc` with a more consistent and useful interface. * The new class `StackVariable` should be used in place of `LocalScopeVariable` diff --git a/change-notes/1.24/analysis-csharp.md b/change-notes/1.24/analysis-csharp.md index 88f4abe91cc..96ed8796543 100644 --- a/change-notes/1.24/analysis-csharp.md +++ b/change-notes/1.24/analysis-csharp.md @@ -29,6 +29,10 @@ The following changes in version 1.24 affect C# analysis in all applications. ## Changes to libraries +* The data-flow library has been improved when flow through methods needs to be + combined with both taint tracking and flow through fields allowing more flow + to be tracked. This affects and improves all security queries, which may + report additional results. * The taint tracking library now tracks flow through (implicit or explicit) conversion operator calls. * [Code contracts](https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/code-contracts) are now recognized, and are treated like any other assertion methods. * Expression nullability flow state is given by the predicates `Expr.hasNotNullFlowState()` and `Expr.hasMaybeNullFlowState()`. diff --git a/change-notes/1.24/analysis-java.md b/change-notes/1.24/analysis-java.md index cfdd157d13f..79e50a7830b 100644 --- a/change-notes/1.24/analysis-java.md +++ b/change-notes/1.24/analysis-java.md @@ -25,6 +25,10 @@ The following changes in version 1.24 affect Java analysis in all applications. ## Changes to libraries +* The data-flow library has been improved when flow through methods needs to be + combined with both taint tracking and flow through fields allowing more flow + to be tracked. This affects and improves all security queries, which may + report additional results. * Identification of test classes has been improved. Previously, one of the match conditions would classify any class with a name containing the string "Test" as a test class, but now this matching has been replaced with one that From ebd2b932e812bf18c20df4732bae2c4bb0d92009 Mon Sep 17 00:00:00 2001 From: ggolawski <35563296+ggolawski@users.noreply.github.com> Date: Wed, 29 Jan 2020 20:05:20 +0100 Subject: [PATCH 082/148] Update java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp Co-Authored-By: Felicity Chapman --- java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp b/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp index 3e2c8656399..af066852d88 100644 --- a/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp +++ b/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp @@ -14,7 +14,7 @@ avoid a malicious user providing special characters that change the meaning of the query. If possible build the LDAP query using framework helper methods, for example from Spring's LdapQueryBuilder and LdapNameBuilder, instead of string concatenation. Alternatively, escape user input using an appropriate -LDAP encoding method, for example: encodeForLDAP or encodeForDN +LDAP encoding method, for example: encodeForLDAP or encodeForDN from OWASP ESAPI, LdapEncoder.filterEncode or LdapEncoder.nameEncode from Spring LDAP, or Filter.encodeValue from UnboundID library.

    From 31743c42e51f358130764ed4c7d5bae1e650b970 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Wed, 29 Jan 2020 20:28:29 +0100 Subject: [PATCH 083/148] Update javascript/ql/src/semmle/javascript/frameworks/Koa.qll Co-Authored-By: Erik Krogh Kristensen --- javascript/ql/src/semmle/javascript/frameworks/Koa.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/Koa.qll b/javascript/ql/src/semmle/javascript/frameworks/Koa.qll index bcc47bad9eb..ab306964ebe 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Koa.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Koa.qll @@ -11,7 +11,7 @@ module Koa { */ class AppDefinition extends HTTP::Servers::StandardServerDefinition, InvokeExpr { AppDefinition() { - // `app = new Koa()` + // `app = new Koa()` / `app = Koa()` this = DataFlow::moduleImport("koa").getAnInvocation().asExpr() } } From 12778812940f43cedfda9a3648072c451601a542 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Wed, 29 Jan 2020 13:07:34 -0700 Subject: [PATCH 084/148] C++: Document `InlineExpectationsTest` --- .../TestUtilities/InlineExpectationsTest.qll | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll b/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll index 8c253b194e4..07235f3e9d7 100644 --- a/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll +++ b/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll @@ -1,11 +1,116 @@ +/** + * Provides a library for writing QL tests whose success or failure is based on expected results + * embedded in the test source code as comments, rather than a `.expected` file. + * + * To create a new inline expectations test: + * - Declare a class that extends `InlineExpectationsTest`. In the characteristic predicate of the + * new class, bind `this` to a unique string (usually the name of the test). + * - Override the `hasActualResult()` predicate to produce the actual results of the query. For each + * result, specify a `Location`, a text description of the element for which the result was + * reported, a short string to serve as the tag to identify expected results for this test, and the + * expected value of the result. + * - Override `getARelevantTag()` to return the set of tags that can be produced by + * `hasActualResult()`. Often this is just a single tag. + * + * Example: + * ``` + * class ConstantValueTest extends InlineExpectationsTest { + * ConstantValueTest() { this = "ConstantValueTest" } + * + * override string getARelevantTag() { + * // We only use one tag for this test. + * result = "const" + * } + * + * override predicate hasActualResult( + * Location location, string element, string tag, string valuesasas + * ) { + * exists(Expr e | + * tag = "const" and // The tag for this test. + * valuesasas = e.getValue() and // The expected value. Will only hold for constant expressions. + * location = e.getLocation() and // The location of the result to be reported. + * element = e.toString() // The display text for the result. + * ) + * } + * } + * ``` + * + * There is no need to write a `select` clause or query predicate. All of the differences between + * expected results and actual results will be reported in the `failures()` query predicate. + * + * To annotate the test source code with an expected result, place a C++-style (`//`) comment on the + * same line as the expected result, with text of the following format as the body of the comment: + * + * `// $tag=expected-value` + * + * Where `tag` is the value of the `tag` parameter from `hasActualResult()`, and `expected-value` is + * the value of the `value` parameter from `hasActualResult()`. Multiple expectations may be placed + * in the same comment, as long as each is prefixed by a `$`. Any actual result that appears on a + * line that does not contain a matching expected result comment will be reported with a message of + * the form "Unexpected result: tag=value". Any expected result comment for which there is no + * matching actual result will be reported with a message of the form + * "Missing result: tag=expected-value". + * + * Example: + * ``` + * int i = x + 5; // $const=5 + * int j = y + (7 - 3) // $const=7 $const=3 $const=4 // The result of the subtraction is a constant. + * ``` + * + * For tests that contain known false positives and false negatives, it is possible to further + * annotate that a particular expected result is known to be a false positive, or that a particular + * missing result is known to be a false negative: + * + * `// $f+:tag=expected-value` // False positive + * `// $f-:tag=expected-value` // False negative + * + * A false positive expectation is treated as any other expected result, except that if there is no + * matching actual result, the message will be of the form "Fixed false positive: tag=value". A + * false negative expectation is treated as if there were no expected result, except that if a + * matching expected result is found, the message will be of the form + * "Fixed false negative: tag=value". + * + * If the same result value is expected for two or more tags on the same line, there is a shorthand + * notation available: + * + * `// $tag1,tag2=expected-value` + * + * is equivalent to: + * + * `// $tag1=expected-value $tag2=expected-value` + */ + import cpp +/** + * Base class for tests with inline expectations. The test extends this class to provide the actual + * results of the query, which are then compared with the expected results in comments to produce a + * list of failure messages that point out where the actual results differ from the expected + * results. + */ abstract class InlineExpectationsTest extends string { bindingset[this] InlineExpectationsTest() { any() } + /** + * Returns all tags that can be generated by this test. Most tests will only ever produce a single + * tag. Any expected result comments for a tag that is not returned by the `getARelevantTag()` + * predicate for an active test will be ignored. This makes it possible to write multiple tests in + * different `.ql` files that all query the same source code. + */ abstract string getARelevantTag(); + /** + * Returns the actual results of the query that is being tested. Each result consist of the + * following values: + * - `location` - The source code location of the result. Any expected result comment must appear + * on the start line of this location. + * - `element` - Display text for the element on which the result is reported. + * - `tag` - The tag that marks this result as coming from this test. This must be one of the tags + * returned by `getARelevantTag()`. + * - `value` - The value of the result, which will be matched against the value associated with + * `tag` in any expected result comment on that line. + */ abstract predicate hasActualResult(Location location, string element, string tag, string value); final predicate hasFailureMessage(FailureLocatable element, string message) { From 46c414b53f349ddbf739a46e57e4af3d33fec033 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Wed, 29 Jan 2020 13:24:55 -0700 Subject: [PATCH 085/148] C++: Document regular expressions in `InlineExpectationsTest` --- .../TestUtilities/InlineExpectationsTest.qll | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll b/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll index 07235f3e9d7..bbd9090c8e6 100644 --- a/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll +++ b/cpp/ql/test/TestUtilities/InlineExpectationsTest.qll @@ -44,11 +44,12 @@ * `// $tag=expected-value` * * Where `tag` is the value of the `tag` parameter from `hasActualResult()`, and `expected-value` is - * the value of the `value` parameter from `hasActualResult()`. Multiple expectations may be placed - * in the same comment, as long as each is prefixed by a `$`. Any actual result that appears on a - * line that does not contain a matching expected result comment will be reported with a message of - * the form "Unexpected result: tag=value". Any expected result comment for which there is no - * matching actual result will be reported with a message of the form + * the value of the `value` parameter from `hasActualResult()`. The `=expected-value` portion may be + * omitted, in which case `expected-value` is treated as the empty string. Multiple expectations may + * be placed in the same comment, as long as each is prefixed by a `$`. Any actual result that + * appears on a line that does not contain a matching expected result comment will be reported with + * a message of the form "Unexpected result: tag=value". Any expected result comment for which there + * is no matching actual result will be reported with a message of the form * "Missing result: tag=expected-value". * * Example: @@ -148,8 +149,19 @@ abstract class InlineExpectationsTest extends string { } } +/** + * RegEx pattern to match a comment containing one or more expected results. The comment must be a + * C++-style (`//`) comment with `$` as its first non-whitespace character. Any subsequent character + * is treated as part of the expected results, except that the comment may contain a `//` sequence + * to treat the remainder of the line as a regular (non-interpreted) comment. + */ private string expectationCommentPattern() { result = "//\\s*(\\$(?:[^/]|/[^/])*)(?://.*)?" } +/** + * RegEx pattern to match a single expected result, not including the leading `$`. It starts with an + * optional `f+:` or `f-:`, followed by one or more comma-separated tags containing only letters, + * `-`, and `_`, optionally followed by `=` and the expected value. + */ private string expectationPattern() { result = "(?:(f(?:\\+|-)):)?((?:[A-Za-z-_]+)(?:\\s*,\\s*[A-Za-z-_]+)*)(?:=(.*))?" } From 790cbf0d6b94b118b5b6e679a3620945a2919b06 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Wed, 29 Jan 2020 17:32:15 -0700 Subject: [PATCH 086/148] C#: Fix bad merge --- .../implementation/unaliased_ssa/internal/AliasAnalysis.qll | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll index 6edca9ccc21..45c945c07ef 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll @@ -84,6 +84,10 @@ private predicate operandIsPropagated(Operand operand, IntValue bitOffset) { bitOffset = Ints::mul(convert.getDerivation().getByteOffset(), 8) ) or + // Conversion using dynamic_cast results in an unknown offset + instr instanceof CheckedConvertOrNullInstruction and + bitOffset = Ints::unknown() + or // Converting to a derived class subtracts the offset of the base class. exists(ConvertToDerivedInstruction convert | convert = instr and From b7a8d0e9034af77c989743ed611da59a0251ae6d Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 30 Jan 2020 10:41:13 +0100 Subject: [PATCH 087/148] Apply suggestions from code review Co-Authored-By: Jonas Jensen --- change-notes/1.24/analysis-cpp.md | 2 +- change-notes/1.24/analysis-csharp.md | 2 +- change-notes/1.24/analysis-java.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/change-notes/1.24/analysis-cpp.md b/change-notes/1.24/analysis-cpp.md index 94c84e991a5..54954e0f70a 100644 --- a/change-notes/1.24/analysis-cpp.md +++ b/change-notes/1.24/analysis-cpp.md @@ -28,7 +28,7 @@ The following changes in version 1.24 affect C/C++ analysis in all applications. * The data-flow library has been improved when flow through functions needs to be combined with both taint tracking and flow through fields allowing more flow - to be tracked. This affects and improves all security queries, which may + to be tracked. This affects and improves some security queries, which may report additional results. * Created the `semmle.code.cpp.models.interfaces.Allocation` library to model allocation such as `new` expressions and calls to `malloc`. This in intended to replace the functionality in `semmle.code.cpp.commons.Alloc` with a more consistent and useful interface. * Created the `semmle.code.cpp.models.interfaces.Deallocation` library to model deallocation such as `delete` expressions and calls to `free`. This in intended to replace the functionality in `semmle.code.cpp.commons.Alloc` with a more consistent and useful interface. diff --git a/change-notes/1.24/analysis-csharp.md b/change-notes/1.24/analysis-csharp.md index 96ed8796543..4efad0ab631 100644 --- a/change-notes/1.24/analysis-csharp.md +++ b/change-notes/1.24/analysis-csharp.md @@ -31,7 +31,7 @@ The following changes in version 1.24 affect C# analysis in all applications. * The data-flow library has been improved when flow through methods needs to be combined with both taint tracking and flow through fields allowing more flow - to be tracked. This affects and improves all security queries, which may + to be tracked. This affects and improves most security queries, which may report additional results. * The taint tracking library now tracks flow through (implicit or explicit) conversion operator calls. * [Code contracts](https://docs.microsoft.com/en-us/dotnet/framework/debug-trace-profile/code-contracts) are now recognized, and are treated like any other assertion methods. diff --git a/change-notes/1.24/analysis-java.md b/change-notes/1.24/analysis-java.md index 79e50a7830b..4bd3145a5c2 100644 --- a/change-notes/1.24/analysis-java.md +++ b/change-notes/1.24/analysis-java.md @@ -27,7 +27,7 @@ The following changes in version 1.24 affect Java analysis in all applications. * The data-flow library has been improved when flow through methods needs to be combined with both taint tracking and flow through fields allowing more flow - to be tracked. This affects and improves all security queries, which may + to be tracked. This affects and improves most security queries, which may report additional results. * Identification of test classes has been improved. Previously, one of the match conditions would classify any class with a name containing the string From 75c549baa1c90157b25ccb6cf08f8df12e14c43e Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 29 Jan 2020 15:52:07 +0100 Subject: [PATCH 088/148] Java: Deprecate ParExpr. --- java/ql/src/Language Abuse/UselessUpcast.ql | 10 ++-- .../src/Likely Bugs/Arithmetic/BadCheckOdd.ql | 8 +-- .../ConstantExpAppearsNonConstant.ql | 15 +++--- .../Likely Bugs/Arithmetic/IntMultToLong.ql | 6 +-- .../DefineEqualsWhenAddingFields.ql | 4 +- .../Comparison/MissingInstanceofInEquals.ql | 2 +- .../Comparison/NoAssignInBooleanExprs.ql | 2 +- .../Comparison/StringComparison.ql | 5 +- .../Comparison/UselessComparisonTest.ql | 4 -- .../Concurrency/DoubleCheckedLocking.qll | 2 - .../Concurrency/NonSynchronizedOverride.ql | 2 - .../Concurrency/SynchSetUnsynchGet.ql | 2 +- .../Likely Typos/ContradictoryTypeChecks.ql | 2 +- .../Likely Bugs/Resource Leaks/CloseType.qll | 2 - .../src/Likely Bugs/Statements/Chaining.qll | 2 +- .../Statements/ReturnValueIgnored.ql | 2 +- .../Security/CWE/CWE-190/ArithmeticCommon.qll | 7 ++- .../CWE/CWE-681/NumericCastCommon.qll | 2 +- .../src/Security/CWE/CWE-835/InfiniteLoop.ql | 2 - .../Boolean Logic/SimplifyBoolExpr.ql | 4 +- .../ConfusingOverloading.ql | 1 - java/ql/src/semmle/code/java/Concurrency.qll | 3 +- .../src/semmle/code/java/ControlFlowGraph.qll | 7 --- java/ql/src/semmle/code/java/Conversions.qll | 2 +- java/ql/src/semmle/code/java/Expr.qll | 49 ++++++++----------- java/ql/src/semmle/code/java/Reflection.qll | 2 +- java/ql/src/semmle/code/java/StringFormat.qll | 5 +- java/ql/src/semmle/code/java/Variable.qll | 2 +- .../code/java/comparison/Comparison.qll | 4 -- .../semmle/code/java/controlflow/Guards.qll | 10 ++-- .../java/controlflow/UnreachableBlocks.qll | 4 -- .../java/controlflow/internal/GuardsLogic.qll | 20 +++----- .../controlflow/internal/Preconditions.qll | 8 +-- .../code/java/dataflow/IntegerGuards.qll | 9 ++-- .../code/java/dataflow/ModulusAnalysis.qll | 2 - .../semmle/code/java/dataflow/NullGuards.qll | 3 -- .../semmle/code/java/dataflow/Nullness.qll | 12 ++--- .../code/java/dataflow/RangeAnalysis.qll | 8 +-- .../semmle/code/java/dataflow/RangeUtils.qll | 6 --- java/ql/src/semmle/code/java/dataflow/SSA.qll | 2 - .../code/java/dataflow/SignAnalysis.qll | 2 - .../semmle/code/java/dataflow/TypeFlow.qll | 2 - .../java/dataflow/internal/DataFlowUtil.qll | 2 - .../code/java/deadcode/DeadEnumConstant.qll | 2 - .../code/java/dispatch/DispatchFlow.qll | 2 - .../src/semmle/code/java/dispatch/ObjFlow.qll | 2 - .../code/java/frameworks/Assertions.qll | 2 +- .../test/library-tests/guards/guardslogic.ql | 1 - 48 files changed, 87 insertions(+), 172 deletions(-) diff --git a/java/ql/src/Language Abuse/UselessUpcast.ql b/java/ql/src/Language Abuse/UselessUpcast.ql index 5c34d4d4cff..18584da159c 100644 --- a/java/ql/src/Language Abuse/UselessUpcast.ql +++ b/java/ql/src/Language Abuse/UselessUpcast.ql @@ -15,7 +15,7 @@ import java predicate usefulUpcast(CastExpr e) { // Upcasts that may be performed to affect resolution of methods or constructors. exists(Call c, int i, Callable target | - c.getArgument(i).getProperExpr() = e and + c.getArgument(i) = e and target = c.getCallee() and // An upcast to the type of the corresponding parameter. e.getType() = target.getParameterType(i) @@ -31,13 +31,13 @@ predicate usefulUpcast(CastExpr e) { ) or // Upcasts of a varargs argument. - exists(Call c, int iArg, int iParam | c.getArgument(iArg).getProperExpr() = e | + exists(Call c, int iArg, int iParam | c.getArgument(iArg) = e | c.getCallee().getParameter(iParam).isVarargs() and iArg >= iParam ) or // Upcasts that are performed on an operand of a ternary expression. exists(ConditionalExpr ce | - e = ce.getTrueExpr().getProperExpr() or e = ce.getFalseExpr().getProperExpr() + e = ce.getTrueExpr() or e = ce.getFalseExpr() ) or // Upcasts to raw types. @@ -46,12 +46,12 @@ predicate usefulUpcast(CastExpr e) { e.getType().(Array).getElementType() instanceof RawType or // Upcasts that are performed to affect field, private method, or static method resolution. - exists(FieldAccess fa | e = fa.getQualifier().getProperExpr() | + exists(FieldAccess fa | e = fa.getQualifier() | not e.getExpr().getType().(RefType).inherits(fa.getField()) ) or exists(MethodAccess ma, Method m | - e = ma.getQualifier().getProperExpr() and + e = ma.getQualifier() and m = ma.getMethod() and (m.isStatic() or m.isPrivate()) | diff --git a/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql index ead6919f8be..70f34bcb077 100644 --- a/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql +++ b/java/ql/src/Likely Bugs/Arithmetic/BadCheckOdd.ql @@ -15,7 +15,7 @@ import java import semmle.code.java.Collections predicate isDefinitelyPositive(Expr e) { - isDefinitelyPositive(e.getProperExpr()) or + isDefinitelyPositive(e) or e.(IntegerLiteral).getIntValue() >= 0 or e.(MethodAccess).getMethod() instanceof CollectionSizeMethod or e.(MethodAccess).getMethod() instanceof StringLengthMethod or @@ -24,10 +24,10 @@ predicate isDefinitelyPositive(Expr e) { from BinaryExpr t, RemExpr lhs, IntegerLiteral rhs, string parity where - t.getLeftOperand().getProperExpr() = lhs and - t.getRightOperand().getProperExpr() = rhs and + t.getLeftOperand() = lhs and + t.getRightOperand() = rhs and not isDefinitelyPositive(lhs.getLeftOperand()) and - lhs.getRightOperand().getProperExpr().(IntegerLiteral).getIntValue() = 2 and + lhs.getRightOperand().(IntegerLiteral).getIntValue() = 2 and ( t instanceof EQExpr and rhs.getIntValue() = 1 and parity = "oddness" or diff --git a/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql index fb55aeb7b2b..eb4e86e65c9 100644 --- a/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql +++ b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql @@ -17,9 +17,6 @@ predicate isConstantExp(Expr e) { // A literal is constant. e instanceof Literal or - // A parenthesized expression is constant if its proper expression is. - isConstantExp(e.(ParExpr).getProperExpr()) - or e instanceof TypeAccess or e instanceof ArrayTypeAccess @@ -33,25 +30,25 @@ predicate isConstantExp(Expr e) { ) or // A cast expression is constant if its expression is. - exists(CastExpr c | c = e | isConstantExp(c.getExpr().getProperExpr())) + exists(CastExpr c | c = e | isConstantExp(c.getExpr())) or // Multiplication by 0 is constant. - exists(MulExpr m | m = e | eval(m.getAnOperand().getProperExpr()) = 0) + exists(MulExpr m | m = e | eval(m.getAnOperand()) = 0) or // Integer remainder by 1 is constant. exists(RemExpr r | r = e | r.getLeftOperand().getType() instanceof IntegralType and - eval(r.getRightOperand().getProperExpr()) = 1 + eval(r.getRightOperand()) = 1 ) or - exists(AndBitwiseExpr a | a = e | eval(a.getAnOperand().getProperExpr()) = 0) + exists(AndBitwiseExpr a | a = e | eval(a.getAnOperand()) = 0) or exists(AndLogicalExpr a | a = e | - a.getAnOperand().getProperExpr().(BooleanLiteral).getBooleanValue() = false + a.getAnOperand().(BooleanLiteral).getBooleanValue() = false ) or exists(OrLogicalExpr o | o = e | - o.getAnOperand().getProperExpr().(BooleanLiteral).getBooleanValue() = true + o.getAnOperand().(BooleanLiteral).getBooleanValue() = true ) } diff --git a/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql index 9371e57b029..d6b7b97199e 100644 --- a/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql +++ b/java/ql/src/Likely Bugs/Arithmetic/IntMultToLong.ql @@ -35,8 +35,8 @@ float exprBound(Expr e) { /** A multiplication that does not overflow. */ predicate small(MulExpr e) { exists(NumType t, float lhs, float rhs, float res | t = e.getType() | - lhs = exprBound(e.getLeftOperand().getProperExpr()) and - rhs = exprBound(e.getRightOperand().getProperExpr()) and + lhs = exprBound(e.getLeftOperand()) and + rhs = exprBound(e.getRightOperand()) and lhs * rhs = res and res <= t.getOrdPrimitiveType().getMaxValue() ) @@ -47,7 +47,7 @@ predicate small(MulExpr e) { */ Expr getRestrictedParent(Expr e) { result = e.getParent() and - (result instanceof ArithExpr or result instanceof ConditionalExpr or result instanceof ParExpr) + (result instanceof ArithExpr or result instanceof ConditionalExpr) } from ConversionSite c, MulExpr e, NumType sourceType, NumType destType diff --git a/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql b/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql index c32095ccbb3..be186d2b2e2 100644 --- a/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql +++ b/java/ql/src/Likely Bugs/Comparison/DefineEqualsWhenAddingFields.ql @@ -32,11 +32,11 @@ predicate checksReferenceEquality(EqualsMethod em) { eq.getAnOperand().(VarAccess).getVariable() = em.getParameter(0) and ( // `{ return (ojb==this); }` - eq = blk.getStmt().(ReturnStmt).getResult().getProperExpr() + eq = blk.getStmt().(ReturnStmt).getResult() or // `{ if (ojb==this) return true; else return false; }` exists(IfStmt ifStmt | ifStmt = blk.getStmt() | - eq = ifStmt.getCondition().getProperExpr() and + eq = ifStmt.getCondition() and ifStmt.getThen().(ReturnStmt).getResult().(BooleanLiteral).getBooleanValue() = true and ifStmt.getElse().(ReturnStmt).getResult().(BooleanLiteral).getBooleanValue() = false ) diff --git a/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql index eb044e4ecb0..eacba3ad4ec 100644 --- a/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql +++ b/java/ql/src/Likely Bugs/Comparison/MissingInstanceofInEquals.ql @@ -45,7 +45,7 @@ class ReferenceEquals extends EqualsMethod { exists(Block b, ReturnStmt ret, EQExpr eq | this.getBody() = b and b.getStmt(0) = ret and - ret.getResult().getProperExpr() = eq and + ret.getResult() = eq and eq.getAnOperand() = this.getAParameter().getAnAccess() and (eq.getAnOperand() instanceof ThisAccess or eq.getAnOperand() instanceof FieldAccess) ) diff --git a/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql b/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql index b04f8de49b5..600257d1c60 100644 --- a/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql +++ b/java/ql/src/Likely Bugs/Comparison/NoAssignInBooleanExprs.ql @@ -26,7 +26,7 @@ class BooleanExpr extends Expr { } private predicate assignAndCheck(AssignExpr e) { - exists(BinaryExpr c | e = c.getAChildExpr().getProperExpr() | + exists(BinaryExpr c | e = c.getAChildExpr() | c instanceof ComparisonExpr or c instanceof EqualityTest ) diff --git a/java/ql/src/Likely Bugs/Comparison/StringComparison.ql b/java/ql/src/Likely Bugs/Comparison/StringComparison.ql index 1bb67c04a67..4681feafc00 100644 --- a/java/ql/src/Likely Bugs/Comparison/StringComparison.ql +++ b/java/ql/src/Likely Bugs/Comparison/StringComparison.ql @@ -24,9 +24,6 @@ class StringValue extends Expr { this.(MethodAccess).getMethod() = intern ) or - // Parenthesized expressions. - this.(ParExpr).getExpr().(StringValue).isInterned() - or // Ternary conditional operator. this.(ConditionalExpr).getTrueExpr().(StringValue).isInterned() and this.(ConditionalExpr).getFalseExpr().(StringValue).isInterned() @@ -52,7 +49,7 @@ predicate variableValuesInterned(Variable v) { not v instanceof Parameter and // If the string is modified with `+=`, then the new string is not interned // even if the components are. - not exists(AssignOp append | append.getDest().getProperExpr() = v.getAnAccess()) + not exists(AssignOp append | append.getDest() = v.getAnAccess()) } from EqualityTest e, StringValue lhs, StringValue rhs diff --git a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql index af37df936e0..964a5a6060a 100644 --- a/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql +++ b/java/ql/src/Likely Bugs/Comparison/UselessComparisonTest.ql @@ -127,8 +127,6 @@ Expr overFlowCand() { or exists(SsaExplicitUpdate x | result = x.getAUse() and x.getDefiningExpr() = overFlowCand()) or - result.(ParExpr).getExpr() = overFlowCand() - or result.(AssignExpr).getRhs() = overFlowCand() or result.(LocalVariableDeclExpr).getInit() = overFlowCand() @@ -165,8 +163,6 @@ Expr increaseOrDecreaseOfVar(SsaVariable v) { result = x.getAUse() and x.getDefiningExpr() = increaseOrDecreaseOfVar(v) ) or - result.(ParExpr).getExpr() = increaseOrDecreaseOfVar(v) - or result.(AssignExpr).getRhs() = increaseOrDecreaseOfVar(v) or result.(LocalVariableDeclExpr).getInit() = increaseOrDecreaseOfVar(v) diff --git a/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLocking.qll b/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLocking.qll index 63293099c5b..ac279049ed1 100644 --- a/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLocking.qll +++ b/java/ql/src/Likely Bugs/Concurrency/DoubleCheckedLocking.qll @@ -13,8 +13,6 @@ private Expr getAFieldRead(Field f) { v.getDefiningExpr().(VariableAssign).getSource() = getAFieldRead(f) ) or - result.(ParExpr).getExpr() = getAFieldRead(f) - or result.(AssignExpr).getSource() = getAFieldRead(f) } diff --git a/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql b/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql index de7e5bc5f6c..b6518ba9bdb 100644 --- a/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql +++ b/java/ql/src/Likely Bugs/Concurrency/NonSynchronizedOverride.ql @@ -27,8 +27,6 @@ predicate delegatingSuperCall(Expr e, Method target) { ) or delegatingSuperCall(e.(CastExpr).getExpr(), target) - or - delegatingSuperCall(e.(ParExpr).getExpr(), target) } /** diff --git a/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql b/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql index df620ca08d8..cbbc07f475c 100644 --- a/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql +++ b/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql @@ -23,7 +23,7 @@ import java */ predicate isSynchronizedByBlock(Method m) { exists(SynchronizedStmt sync, Expr on | - sync = m.getBody().getAChild*() and on = sync.getExpr().getProperExpr() + sync = m.getBody().getAChild*() and on = sync.getExpr() | if m.isStatic() then on.(TypeLiteral).getTypeName().getType() = m.getDeclaringType() diff --git a/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql index 6a9fc9b7051..73cb99be0c3 100644 --- a/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql +++ b/java/ql/src/Likely Bugs/Likely Typos/ContradictoryTypeChecks.ql @@ -17,7 +17,7 @@ import semmle.code.java.dataflow.SSA /** `ioe` is of the form `va instanceof t`. */ predicate instanceOfCheck(InstanceOfExpr ioe, VarAccess va, RefType t) { - ioe.getExpr().getProperExpr() = va and + ioe.getExpr() = va and ioe.getTypeName().getType().(RefType).getSourceDeclaration() = t } diff --git a/java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll b/java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll index c319f2568d6..16f8d6f8bcc 100644 --- a/java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll +++ b/java/ql/src/Likely Bugs/Resource Leaks/CloseType.qll @@ -9,8 +9,6 @@ import semmle.code.java.frameworks.Mockito private predicate flowsInto(Expr e, Variable v) { e = v.getAnAssignedValue() or - exists(ParExpr p | flowsInto(p, v) | e = p.getExpr()) - or exists(CastExpr c | flowsInto(c, v) | e = c.getExpr()) or exists(ConditionalExpr c | flowsInto(c, v) | e = c.getTrueExpr() or e = c.getFalseExpr()) diff --git a/java/ql/src/Likely Bugs/Statements/Chaining.qll b/java/ql/src/Likely Bugs/Statements/Chaining.qll index 315f0e32c09..1e1c7187e30 100644 --- a/java/ql/src/Likely Bugs/Statements/Chaining.qll +++ b/java/ql/src/Likely Bugs/Statements/Chaining.qll @@ -48,7 +48,7 @@ private predicate nonChainingReturn(Method m, ReturnStmt ret) { or // A method on the wrong object is called. not ( - delegateCall.getQualifier().getProperExpr() instanceof ThisAccess or + delegateCall.getQualifier() instanceof ThisAccess or not exists(delegateCall.getQualifier()) ) or diff --git a/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql b/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql index 88d786a7159..d2c2c555ae2 100644 --- a/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql +++ b/java/ql/src/Likely Bugs/Statements/ReturnValueIgnored.ql @@ -78,7 +78,7 @@ predicate relevantMethodCall(MethodAccess ma, Method m) { ma.getMethod() = m and not m.getReturnType().hasName("void") and (not isMockingMethod(m) or isMustBeQualifierMockingMethod(m)) and - not isMockingMethod(ma.getQualifier().getProperExpr().(MethodAccess).getMethod()) + not isMockingMethod(ma.getQualifier().(MethodAccess).getMethod()) } predicate methodStats(Method m, int used, int total, int percentage) { diff --git a/java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll b/java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll index c59c6376ae9..9d081e34420 100644 --- a/java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll +++ b/java/ql/src/Security/CWE/CWE-190/ArithmeticCommon.qll @@ -14,7 +14,7 @@ private import semmle.code.java.controlflow.internal.GuardsLogic predicate narrowerThanOrEqualTo(ArithExpr exp, NumType numType) { exp.getType().(NumType).widerThan(numType) implies - exists(CastExpr cast | cast.getAChildExpr().getProperExpr() = exp | + exists(CastExpr cast | cast.getAChildExpr() = exp | numType.widerThanOrEqualTo(cast.getType().(NumType)) ) } @@ -54,11 +54,11 @@ private Guard sizeGuard(SsaVariable v, boolean branch, boolean upper) { positive(pos) and upper = true | - comp.getLesserOperand().getProperExpr() = add and + comp.getLesserOperand() = add and comp.getGreaterOperand().(IntegerLiteral).getIntValue() = 0 and branch = false or - comp.getGreaterOperand().getProperExpr() = add and + comp.getGreaterOperand() = add and comp.getLesserOperand().(IntegerLiteral).getIntValue() = 0 and branch = true ) @@ -157,7 +157,6 @@ predicate upcastToWiderType(Expr e) { /** Holds if the result of `exp` has certain bits filtered by a bitwise and. */ private predicate inBitwiseAnd(Expr exp) { exists(AndBitwiseExpr a | a.getAnOperand() = exp) or - inBitwiseAnd(exp.(ParExpr).getExpr()) or inBitwiseAnd(exp.(LShiftExpr).getAnOperand()) or inBitwiseAnd(exp.(RShiftExpr).getAnOperand()) or inBitwiseAnd(exp.(URShiftExpr).getAnOperand()) diff --git a/java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll b/java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll index e66e71ebd9f..4887c3fefad 100644 --- a/java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll +++ b/java/ql/src/Security/CWE/CWE-681/NumericCastCommon.qll @@ -29,7 +29,7 @@ class RightShiftOp extends Expr { Variable getShiftedVariable() { getLhs() = result.getAnAccess() or - getLhs().getProperExpr().(AndBitwiseExpr).getAnOperand() = result.getAnAccess() + getLhs().(AndBitwiseExpr).getAnOperand() = result.getAnAccess() } } diff --git a/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql b/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql index 728d2b17930..cc02dfb3f09 100644 --- a/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql +++ b/java/ql/src/Security/CWE/CWE-835/InfiniteLoop.ql @@ -48,8 +48,6 @@ predicate subCondition(Expr cond, Expr subcond, boolean negated) { or subCondition(cond.(OrLogicalExpr).getAnOperand(), subcond, negated) or - subCondition(cond.(ParExpr).getExpr(), subcond, negated) - or subCondition(cond.(LogNotExpr).getExpr(), subcond, negated.booleanNot()) } diff --git a/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql b/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql index db8fc26614e..178f5dbd847 100644 --- a/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql +++ b/java/ql/src/Violations of Best Practice/Boolean Logic/SimplifyBoolExpr.ql @@ -84,9 +84,9 @@ where or conditionalWithBool(e, pattern, rewrite) or - e.(LogNotExpr).getExpr().getProperExpr().(ComparisonOrEquality).negate(pattern, rewrite) + e.(LogNotExpr).getExpr().(ComparisonOrEquality).negate(pattern, rewrite) or - e.(LogNotExpr).getExpr().getProperExpr() instanceof LogNotExpr and + e.(LogNotExpr).getExpr() instanceof LogNotExpr and pattern = "!!A" and rewrite = "A" select e, "Expressions of the form \"" + pattern + "\" can be simplified to \"" + rewrite + "\"." diff --git a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql index 07c071ad2d4..1d8509c6e03 100644 --- a/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql +++ b/java/ql/src/Violations of Best Practice/Naming Conventions/ConfusingOverloading.ql @@ -84,7 +84,6 @@ private predicate confusinglyOverloaded(Method m, Method n) { private predicate wrappedAccess(Expr e, MethodAccess ma) { e = ma or - wrappedAccess(e.(ParExpr).getExpr(), ma) or wrappedAccess(e.(CastExpr).getExpr(), ma) } diff --git a/java/ql/src/semmle/code/java/Concurrency.qll b/java/ql/src/semmle/code/java/Concurrency.qll index 29202c8cc40..61e76525ec8 100644 --- a/java/ql/src/semmle/code/java/Concurrency.qll +++ b/java/ql/src/semmle/code/java/Concurrency.qll @@ -14,8 +14,7 @@ predicate locallySynchronizedOn(Expr e, SynchronizedStmt sync, Variable v) { */ predicate locallySynchronizedOnThis(Expr e, RefType thisType) { exists(SynchronizedStmt sync | e.getEnclosingStmt().getEnclosingStmt+() = sync | - sync.getExpr().getProperExpr().(ThisAccess).getType().(RefType).getSourceDeclaration() = - thisType + sync.getExpr().(ThisAccess).getType().(RefType).getSourceDeclaration() = thisType ) or exists(SynchronizedCallable c | c = e.getEnclosingCallable() | diff --git a/java/ql/src/semmle/code/java/ControlFlowGraph.qll b/java/ql/src/semmle/code/java/ControlFlowGraph.qll index 9c7cc976d46..13e6524352a 100644 --- a/java/ql/src/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/src/semmle/code/java/ControlFlowGraph.qll @@ -289,8 +289,6 @@ private module ControlFlowGraphImpl { logexpr.(UnaryExpr).getExpr() = b and inBooleanContext(logexpr) ) or - exists(ParExpr parexpr | parexpr.getExpr() = b and inBooleanContext(parexpr)) - or exists(ConditionalExpr condexpr | condexpr.getCondition() = b or @@ -574,8 +572,6 @@ private module ControlFlowGraphImpl { or result = first(n.(PostOrderNode).firstChild()) or - result = first(n.(ParExpr).getExpr()) - or result = first(n.(SynchronizedStmt).getExpr()) or result = n and @@ -708,9 +704,6 @@ private module ControlFlowGraphImpl { last(condexpr.getTrueExpr(), last, completion) ) or - // Parentheses are skipped in the CFG. - last(n.(ParExpr).getExpr(), last, completion) - or // The last node of a node executed in post-order is the node itself. n.(PostOrderNode).mayCompleteNormally() and last = n and completion = NormalCompletion() or diff --git a/java/ql/src/semmle/code/java/Conversions.qll b/java/ql/src/semmle/code/java/Conversions.qll index 460b2c24ff2..9d55f1297fc 100644 --- a/java/ql/src/semmle/code/java/Conversions.qll +++ b/java/ql/src/semmle/code/java/Conversions.qll @@ -49,7 +49,7 @@ class AssignmentConversionContext extends ConversionSite { AssignmentConversionContext() { this = v.getAnAssignedValue() or - exists(Assignment a | a.getDest().getProperExpr() = v.getAnAccess() and this = a.getSource()) + exists(Assignment a | a.getDest() = v.getAnAccess() and this = a.getSource()) } override Type getConversionTarget() { result = v.getType() } diff --git a/java/ql/src/semmle/code/java/Expr.qll b/java/ql/src/semmle/code/java/Expr.qll index 51312dd1dc5..08f869c01f2 100755 --- a/java/ql/src/semmle/code/java/Expr.qll +++ b/java/ql/src/semmle/code/java/Expr.qll @@ -46,8 +46,12 @@ class Expr extends ExprParent, @expr { */ int getKind() { exprs(this, result, _, _, _) } - /** Gets this expression with any surrounding parentheses removed. */ - Expr getProperExpr() { + /** + * DEPRECATED: This is no longer necessary. See `Expr.isParenthesized()`. + * + * Gets this expression with any surrounding parentheses removed. + */ + deprecated Expr getProperExpr() { result = this.(ParExpr).getExpr().getProperExpr() or result = this and not this instanceof ParExpr @@ -152,9 +156,6 @@ class CompileTimeConstantExpr extends Expr { e.getFalseExpr().isCompileTimeConstant() ) or - // Parenthesized expressions whose contained expression is a constant expression. - this.(ParExpr).getExpr().isCompileTimeConstant() - or // Access to a final variable initialized by a compile-time constant. exists(Variable v | this = v.getAnAccess() | v.isFinal() and @@ -169,8 +170,6 @@ class CompileTimeConstantExpr extends Expr { string getStringValue() { result = this.(StringLiteral).getRepresentedString() or - result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getStringValue() - or result = this.(AddExpr).getLeftOperand().(CompileTimeConstantExpr).getStringValue() + this.(AddExpr).getRightOperand().(CompileTimeConstantExpr).getStringValue() @@ -296,9 +295,6 @@ class CompileTimeConstantExpr extends Expr { else result = ce.getFalseExpr().(CompileTimeConstantExpr).getBooleanValue() ) or - // Parenthesized expressions containing a boolean value. - result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getBooleanValue() - or // Simple or qualified names where the variable is final and the initializer is a constant. exists(Variable v | this = v.getAnAccess() | result = v.getInitializer().(CompileTimeConstantExpr).getBooleanValue() @@ -385,8 +381,6 @@ class CompileTimeConstantExpr extends Expr { else result = ce.getFalseExpr().(CompileTimeConstantExpr).getIntValue() ) or - result = this.(ParExpr).getExpr().(CompileTimeConstantExpr).getIntValue() - or // If a `Variable` is a `CompileTimeConstantExpr`, its value is its initializer. exists(Variable v | this = v.getAnAccess() | result = v.getInitializer().(CompileTimeConstantExpr).getIntValue() @@ -640,12 +634,8 @@ class BinaryExpr extends Expr, @binaryexpr { /** Gets the operand on the right-hand side of this binary expression. */ Expr getRightOperand() { result.isNthChildOf(this, 1) } - /** Gets an operand (left or right), with any parentheses removed. */ - Expr getAnOperand() { - exists(Expr r | r = this.getLeftOperand() or r = this.getRightOperand() | - result = r.getProperExpr() - ) - } + /** Gets an operand (left or right). */ + Expr getAnOperand() { result = this.getLeftOperand() or result = this.getRightOperand() } /** The operands of this binary expression are `e` and `f`, in either order. */ predicate hasOperands(Expr e, Expr f) { @@ -761,17 +751,14 @@ class NEExpr extends BinaryExpr, @neexpr { * A bitwise expression. * * This includes expressions involving the operators - * `&`, `|`, `^`, or `~`, - * possibly parenthesized. + * `&`, `|`, `^`, or `~`. */ class BitwiseExpr extends Expr { BitwiseExpr() { - exists(Expr proper | proper = this.getProperExpr() | - proper instanceof AndBitwiseExpr or - proper instanceof OrBitwiseExpr or - proper instanceof XorBitwiseExpr or - proper instanceof BitNotExpr - ) + this instanceof AndBitwiseExpr or + this instanceof OrBitwiseExpr or + this instanceof XorBitwiseExpr or + this instanceof BitNotExpr } } @@ -1128,10 +1115,14 @@ class SwitchExpr extends Expr, @switchexpr { override string toString() { result = "switch (...)" } } -/** A parenthesised expression. */ -class ParExpr extends Expr, @parexpr { +/** + * DEPRECATED: Use `Expr.isParenthesized()` instead. + * + * A parenthesised expression. + */ +deprecated class ParExpr extends Expr, @parexpr { /** Gets the expression inside the parentheses. */ - Expr getExpr() { result.getParent() = this } + deprecated Expr getExpr() { result.getParent() = this } /** Gets a printable representation of this expression. */ override string toString() { result = "(...)" } diff --git a/java/ql/src/semmle/code/java/Reflection.qll b/java/ql/src/semmle/code/java/Reflection.qll index 0c6432e2409..3b0189e17ae 100644 --- a/java/ql/src/semmle/code/java/Reflection.qll +++ b/java/ql/src/semmle/code/java/Reflection.qll @@ -284,7 +284,7 @@ class NewInstance extends MethodAccess { * cast. */ private Type getCastInferredConstructedTypes() { - exists(CastExpr cast | cast.getExpr() = this or cast.getExpr().(ParExpr).getExpr() = this | + exists(CastExpr cast | cast.getExpr() = this | result = cast.getType() or // If we cast the result of this method, then this is either the type specified, or a diff --git a/java/ql/src/semmle/code/java/StringFormat.qll b/java/ql/src/semmle/code/java/StringFormat.qll index 075471d73ca..410d83818cc 100644 --- a/java/ql/src/semmle/code/java/StringFormat.qll +++ b/java/ql/src/semmle/code/java/StringFormat.qll @@ -246,8 +246,7 @@ private predicate formatStringFragment(Expr fmt) { e.(AddExpr).getLeftOperand() = fmt or e.(AddExpr).getRightOperand() = fmt or e.(ConditionalExpr).getTrueExpr() = fmt or - e.(ConditionalExpr).getFalseExpr() = fmt or - e.(ParExpr).getExpr() = fmt + e.(ConditionalExpr).getFalseExpr() = fmt ) } @@ -268,8 +267,6 @@ private predicate formatStringValue(Expr e, string fmtvalue) { or e.getType() instanceof EnumType and fmtvalue = "x" // dummy value or - formatStringValue(e.(ParExpr).getExpr(), fmtvalue) - or exists(Variable v | e = v.getAnAccess() and v.isFinal() and diff --git a/java/ql/src/semmle/code/java/Variable.qll b/java/ql/src/semmle/code/java/Variable.qll index 9a281786ac4..d63bcc6fe1c 100755 --- a/java/ql/src/semmle/code/java/Variable.qll +++ b/java/ql/src/semmle/code/java/Variable.qll @@ -17,7 +17,7 @@ class Variable extends @variable, Annotatable, Element, Modifiable { exists(LocalVariableDeclExpr e | e.getVariable() = this and result = e.getInit()) or exists(AssignExpr e | - e.getDest().getProperExpr() = this.getAnAccess() and result = e.getSource() + e.getDest() = this.getAnAccess() and result = e.getSource() ) } diff --git a/java/ql/src/semmle/code/java/comparison/Comparison.qll b/java/ql/src/semmle/code/java/comparison/Comparison.qll index 8a869d8a2e4..27ed9271e99 100644 --- a/java/ql/src/semmle/code/java/comparison/Comparison.qll +++ b/java/ql/src/semmle/code/java/comparison/Comparison.qll @@ -6,10 +6,6 @@ import java * Used as basis for the transitive closure in `exprImplies`. */ private predicate exprImpliesStep(Expr e1, boolean b1, Expr e2, boolean b2) { - e1.(ParExpr).getProperExpr() = e2 and - b2 = b1 and - (b1 = true or b1 = false) - or e1.(LogNotExpr).getExpr() = e2 and b2 = b1.booleanNot() and (b1 = true or b1 = false) diff --git a/java/ql/src/semmle/code/java/controlflow/Guards.qll b/java/ql/src/semmle/code/java/controlflow/Guards.qll index c382e7963cc..82cb1d997ae 100644 --- a/java/ql/src/semmle/code/java/controlflow/Guards.qll +++ b/java/ql/src/semmle/code/java/controlflow/Guards.qll @@ -103,8 +103,8 @@ class Guard extends ExprParent { */ BasicBlock getBasicBlock() { result = this.(Expr).getBasicBlock() or - result = this.(SwitchCase).getSwitch().getExpr().getProperExpr().getBasicBlock() or - result = this.(SwitchCase).getSwitchExpr().getExpr().getProperExpr().getBasicBlock() + result = this.(SwitchCase).getSwitch().getExpr().getBasicBlock() or + result = this.(SwitchCase).getSwitchExpr().getExpr().getBasicBlock() } /** @@ -115,9 +115,9 @@ class Guard extends ExprParent { */ predicate isEquality(Expr e1, Expr e2, boolean polarity) { exists(Expr exp1, Expr exp2 | equalityGuard(this, exp1, exp2, polarity) | - e1 = exp1.getProperExpr() and e2 = exp2.getProperExpr() + e1 = exp1 and e2 = exp2 or - e2 = exp1.getProperExpr() and e1 = exp2.getProperExpr() + e2 = exp1 and e1 = exp2 ) } @@ -265,7 +265,7 @@ private predicate equalityGuard(Guard g, Expr e1, Expr e2, boolean polarity) { exists(ConstCase cc | cc = g and polarity = true and - cc.getSelectorExpr().getProperExpr() = e1 and + cc.getSelectorExpr() = e1 and cc.getValue() = e2 and strictcount(cc.getValue(_)) = 1 ) diff --git a/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll b/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll index c7be6d2494d..5ce27fda434 100644 --- a/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll +++ b/java/ql/src/semmle/code/java/controlflow/UnreachableBlocks.qll @@ -93,8 +93,6 @@ class ConstantExpr extends Expr { // A binary expression where both sides are constant this.(BinaryExpr).getLeftOperand() instanceof ConstantExpr and this.(BinaryExpr).getRightOperand() instanceof ConstantExpr - or - this.(ParExpr).getExpr() instanceof ConstantExpr ) } @@ -108,8 +106,6 @@ class ConstantExpr extends Expr { or result = this.(FieldRead).getField().(ConstantField).getConstantValue().getBooleanValue() or - result = this.(ParExpr).getExpr().(ConstantExpr).getBooleanValue() - or // Handle binary expressions that have integer operands and a boolean result. exists(BinaryExpr b, int left, int right | b = this and diff --git a/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll b/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll index 6fd8d08a658..ba2fd8c01c0 100644 --- a/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll +++ b/java/ql/src/semmle/code/java/controlflow/internal/GuardsLogic.qll @@ -19,10 +19,6 @@ private import semmle.code.java.dataflow.IntegerGuards * Restricted to BaseSSA-based reasoning. */ predicate implies_v1(Guard g1, boolean b1, Guard g2, boolean b2) { - g1.(ParExpr).getExpr() = g2 and - b1 = b2 and - (b1 = true or b1 = false) - or g1.(AndBitwiseExpr).getAnOperand() = g2 and b1 = true and b2 = true or g1.(OrBitwiseExpr).getAnOperand() = g2 and b1 = false and b2 = false @@ -220,13 +216,11 @@ private predicate hasPossibleUnknownValue(SsaVariable v) { * `ConditionalExpr`s. Parentheses are also removed. */ private Expr possibleValue(Expr e) { - result = possibleValue(e.(ParExpr).getExpr()) - or result = possibleValue(e.(ConditionalExpr).getTrueExpr()) or result = possibleValue(e.(ConditionalExpr).getFalseExpr()) or - result = e and not e instanceof ParExpr and not e instanceof ConditionalExpr + result = e and not e instanceof ConditionalExpr } /** @@ -253,7 +247,7 @@ private predicate possibleValue(SsaVariable v, boolean fromBackEdge, Expr e, Abs not hasPossibleUnknownValue(v) and exists(SsaExplicitUpdate def | def = getADefinition(v, fromBackEdge) and - e = possibleValue(def.getDefiningExpr().(VariableAssign).getSource().getProperExpr()) and + e = possibleValue(def.getDefiningExpr().(VariableAssign).getSource()) and k.getExpr() = e ) } @@ -305,7 +299,7 @@ private predicate guardControlsPhiBranch( SsaExplicitUpdate upd, SsaPhiNode phi, Guard guard, boolean branch, Expr e ) { guard.directlyControls(upd.getBasicBlock(), branch) and - upd.getDefiningExpr().(VariableAssign).getSource().getProperExpr() = e and + upd.getDefiningExpr().(VariableAssign).getSource() = e and upd = phi.getAPhiInput() and guard.getBasicBlock().bbStrictlyDominates(phi.getBasicBlock()) } @@ -319,12 +313,12 @@ private predicate guardControlsPhiBranch( */ private predicate conditionalAssign(SsaVariable v, Guard guard, boolean branch, Expr e) { exists(ConditionalExpr c | - v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource().getProperExpr() = c and - guard = c.getCondition().getProperExpr() + v.(SsaExplicitUpdate).getDefiningExpr().(VariableAssign).getSource() = c and + guard = c.getCondition() | - branch = true and e = c.getTrueExpr().getProperExpr() + branch = true and e = c.getTrueExpr() or - branch = false and e = c.getFalseExpr().getProperExpr() + branch = false and e = c.getFalseExpr() ) or exists(SsaExplicitUpdate upd, SsaPhiNode phi | diff --git a/java/ql/src/semmle/code/java/controlflow/internal/Preconditions.qll b/java/ql/src/semmle/code/java/controlflow/internal/Preconditions.qll index 9f7fbc14aa4..0fd367d65b4 100644 --- a/java/ql/src/semmle/code/java/controlflow/internal/Preconditions.qll +++ b/java/ql/src/semmle/code/java/controlflow/internal/Preconditions.qll @@ -24,9 +24,9 @@ predicate conditionCheckMethod(Method m, boolean checkTrue) { not m.isOverridable() and p.getType() instanceof BooleanType and m.getBody().getStmt(0) = ifstmt and - ifstmt.getCondition().getProperExpr() = cond and + ifstmt.getCondition() = cond and ( - cond.(LogNotExpr).getExpr().getProperExpr().(VarAccess).getVariable() = p and checkTrue = true + cond.(LogNotExpr).getExpr().(VarAccess).getVariable() = p and checkTrue = true or cond.(VarAccess).getVariable() = p and checkTrue = false ) and @@ -41,9 +41,9 @@ predicate conditionCheckMethod(Method m, boolean checkTrue) { not m.isOverridable() and m.getBody().getStmt(0).(ExprStmt).getExpr() = ma and conditionCheck(ma, ct) and - ma.getArgument(0).getProperExpr() = arg and + ma.getArgument(0) = arg and ( - arg.(LogNotExpr).getExpr().getProperExpr().(VarAccess).getVariable() = p and + arg.(LogNotExpr).getExpr().(VarAccess).getVariable() = p and checkTrue = ct.booleanNot() or arg.(VarAccess).getVariable() = p and checkTrue = ct diff --git a/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll b/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll index 5fb592272fb..29e2b3ec8a1 100644 --- a/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll +++ b/java/ql/src/semmle/code/java/dataflow/IntegerGuards.qll @@ -10,7 +10,6 @@ private import RangeAnalysis /** Gets an expression that might have the value `i`. */ private Expr exprWithIntValue(int i) { result.(ConstantIntegerExpr).getIntValue() = i or - result.(ParExpr).getExpr() = exprWithIntValue(i) or result.(ConditionalExpr).getTrueExpr() = exprWithIntValue(i) or result.(ConditionalExpr).getFalseExpr() = exprWithIntValue(i) } @@ -142,25 +141,25 @@ Expr intBoundGuard(RValue x, boolean branch_with_lower_bound_k, int k) { x.getVariable().getType() instanceof IntegralType | // c < x - comp.getLesserOperand().getProperExpr() = c and + comp.getLesserOperand() = c and comp.isStrict() and branch_with_lower_bound_k = true and val + 1 = k or // c <= x - comp.getLesserOperand().getProperExpr() = c and + comp.getLesserOperand() = c and not comp.isStrict() and branch_with_lower_bound_k = true and val = k or // x < c - comp.getGreaterOperand().getProperExpr() = c and + comp.getGreaterOperand() = c and comp.isStrict() and branch_with_lower_bound_k = false and val = k or // x <= c - comp.getGreaterOperand().getProperExpr() = c and + comp.getGreaterOperand() = c and not comp.isStrict() and branch_with_lower_bound_k = false and val + 1 = k diff --git a/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll index 876c746d7c0..1fb3eb201a8 100644 --- a/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll +++ b/java/ql/src/semmle/code/java/dataflow/ModulusAnalysis.qll @@ -76,8 +76,6 @@ private Expr modExpr(Expr arg, int mod) { c.getIntValue() = mod - 1 and result.(AndBitwiseExpr).hasOperands(arg, c) ) - or - result.(ParExpr).getExpr() = modExpr(arg, mod) } /** diff --git a/java/ql/src/semmle/code/java/dataflow/NullGuards.qll b/java/ql/src/semmle/code/java/dataflow/NullGuards.qll index b30c6f70f18..e4e7ec33e2c 100644 --- a/java/ql/src/semmle/code/java/dataflow/NullGuards.qll +++ b/java/ql/src/semmle/code/java/dataflow/NullGuards.qll @@ -11,7 +11,6 @@ private import IntegerGuards /** Gets an expression that is always `null`. */ Expr alwaysNullExpr() { result instanceof NullLiteral or - result.(ParExpr).getExpr() = alwaysNullExpr() or result.(CastExpr).getExpr() = alwaysNullExpr() } @@ -60,8 +59,6 @@ Expr clearlyNotNullExpr(Expr reason) { reason = result ) or - result.(ParExpr).getExpr() = clearlyNotNullExpr(reason) - or result.(CastExpr).getExpr() = clearlyNotNullExpr(reason) or result.(AssignExpr).getSource() = clearlyNotNullExpr(reason) diff --git a/java/ql/src/semmle/code/java/dataflow/Nullness.qll b/java/ql/src/semmle/code/java/dataflow/Nullness.qll index c3faeaf0ff2..644790bea6b 100644 --- a/java/ql/src/semmle/code/java/dataflow/Nullness.qll +++ b/java/ql/src/semmle/code/java/dataflow/Nullness.qll @@ -45,7 +45,6 @@ private import semmle.code.java.frameworks.Assertions /** Gets an expression that may be `null`. */ Expr nullExpr() { result instanceof NullLiteral or - result.(ParExpr).getExpr() = nullExpr() or result.(ConditionalExpr).getTrueExpr() = nullExpr() or result.(ConditionalExpr).getFalseExpr() = nullExpr() or result.(AssignExpr).getSource() = nullExpr() or @@ -131,11 +130,8 @@ predicate dereference(Expr e) { * The `VarAccess` is included for nicer error reporting. */ private ControlFlowNode varDereference(SsaVariable v, VarAccess va) { - exists(Expr e | - dereference(e) and - e = sameValue(v, va) and - result = e.getProperExpr() - ) + dereference(result) and + result = sameValue(v, va) } /** @@ -442,8 +438,8 @@ private predicate nullDerefCandidate(SsaVariable origin, VarAccess va) { /** A variable that is assigned `null` if the given condition takes the given branch. */ private predicate varConditionallyNull(SsaExplicitUpdate v, ConditionBlock cond, boolean branch) { exists(ConditionalExpr condexpr | - v.getDefiningExpr().(VariableAssign).getSource().getProperExpr() = condexpr and - condexpr.getCondition().getProperExpr() = cond.getCondition() + v.getDefiningExpr().(VariableAssign).getSource() = condexpr and + condexpr.getCondition() = cond.getCondition() | condexpr.getTrueExpr() = nullExpr() and branch = true and diff --git a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll index e6b2e22d83c..435b976fde2 100644 --- a/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll +++ b/java/ql/src/semmle/code/java/dataflow/RangeAnalysis.qll @@ -131,7 +131,7 @@ private predicate boundCondition( or exists(SubExpr sub, ConstantIntegerExpr c, int d | // (v - d) - e < c - comp.getLesserOperand().getProperExpr() = sub and + comp.getLesserOperand() = sub and comp.getGreaterOperand() = c and sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and @@ -139,7 +139,7 @@ private predicate boundCondition( delta = d + c.getIntValue() or // (v - d) - e > c - comp.getGreaterOperand().getProperExpr() = sub and + comp.getGreaterOperand() = sub and comp.getLesserOperand() = c and sub.getLeftOperand() = ssaRead(v, d) and sub.getRightOperand() = e and @@ -147,7 +147,7 @@ private predicate boundCondition( delta = d + c.getIntValue() or // e - (v - d) < c - comp.getLesserOperand().getProperExpr() = sub and + comp.getLesserOperand() = sub and comp.getGreaterOperand() = c and sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and @@ -155,7 +155,7 @@ private predicate boundCondition( delta = d - c.getIntValue() or // e - (v - d) > c - comp.getGreaterOperand().getProperExpr() = sub and + comp.getGreaterOperand() = sub and comp.getLesserOperand() = c and sub.getLeftOperand() = e and sub.getRightOperand() = ssaRead(v, d) and diff --git a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll index 08d46f6bb80..22e363d9e3c 100644 --- a/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll +++ b/java/ql/src/semmle/code/java/dataflow/RangeUtils.qll @@ -26,8 +26,6 @@ private predicate nonNullSsaFwdStep(SsaVariable v, SsaVariable phi) { } private predicate nonNullDefStep(Expr e1, Expr e2) { - e2.(ParExpr).getExpr() = e1 - or exists(ConditionalExpr cond | cond = e2 | cond.getTrueExpr() = e1 and cond.getFalseExpr() instanceof NullLiteral or @@ -104,8 +102,6 @@ class ConstantIntegerExpr extends Expr { Expr ssaRead(SsaVariable v, int delta) { 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() @@ -264,8 +260,6 @@ 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 diff --git a/java/ql/src/semmle/code/java/dataflow/SSA.qll b/java/ql/src/semmle/code/java/dataflow/SSA.qll index ee397dd478f..561fdb35c5e 100644 --- a/java/ql/src/semmle/code/java/dataflow/SSA.qll +++ b/java/ql/src/semmle/code/java/dataflow/SSA.qll @@ -1129,7 +1129,5 @@ Expr sameValue(SsaVariable v, VarAccess va) { or result.(AssignExpr).getSource() = sameValue(v, va) or - result.(ParExpr).getExpr() = sameValue(v, va) - or result.(RefTypeCastExpr).getExpr() = sameValue(v, va) } diff --git a/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll b/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll index 7e9a22dbb85..60758069232 100644 --- a/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll +++ b/java/ql/src/semmle/code/java/dataflow/SignAnalysis.qll @@ -476,8 +476,6 @@ private Sign exprSign(Expr e) { ( unknownSign(e) or - result = exprSign(e.(ParExpr).getExpr()) - or exists(SsaVariable v | v.getAUse() = e | result = ssaSign(v, any(SsaReadPositionBlock bb | bb.getBlock() = e.getBasicBlock())) or diff --git a/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll index ec3d14b8159..e9f32aadbbe 100644 --- a/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll @@ -109,8 +109,6 @@ private predicate step(TypeFlowNode n1, TypeFlowNode n2) { or n2.asExpr() = n1.asSsa().getAUse() or - n2.asExpr().(ParExpr).getExpr() = n1.asExpr() - or n2.asExpr().(CastExpr).getExpr() = n1.asExpr() and not n2.asExpr().getType() instanceof PrimitiveType or diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll index ffa1a86f688..889e0c0c8a6 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowUtil.qll @@ -390,8 +390,6 @@ predicate simpleLocalFlowStep(Node node1, Node node2) { or ThisFlow::adjacentThisRefs(node1.(PostUpdateNode).getPreUpdateNode(), node2) or - node2.asExpr().(ParExpr).getExpr() = node1.asExpr() - or node2.asExpr().(CastExpr).getExpr() = node1.asExpr() or node2.asExpr().(ConditionalExpr).getTrueExpr() = node1.asExpr() diff --git a/java/ql/src/semmle/code/java/deadcode/DeadEnumConstant.qll b/java/ql/src/semmle/code/java/deadcode/DeadEnumConstant.qll index 1c19a9e20cf..c29b167d31e 100644 --- a/java/ql/src/semmle/code/java/deadcode/DeadEnumConstant.qll +++ b/java/ql/src/semmle/code/java/deadcode/DeadEnumConstant.qll @@ -11,8 +11,6 @@ Expr valueFlow(Expr src) { src = c.getTrueExpr() or src = c.getFalseExpr() ) - or - src = result.(ParExpr).getExpr() } /** diff --git a/java/ql/src/semmle/code/java/dispatch/DispatchFlow.qll b/java/ql/src/semmle/code/java/dispatch/DispatchFlow.qll index 4194a4cd97c..0b2b871e129 100644 --- a/java/ql/src/semmle/code/java/dispatch/DispatchFlow.qll +++ b/java/ql/src/semmle/code/java/dispatch/DispatchFlow.qll @@ -192,8 +192,6 @@ private predicate flowStep(RelevantNode n1, RelevantNode n2) { n2.asExpr().(MethodAccess).getMethod() = getValue ) or - n2.asExpr().(ParExpr).getExpr() = n1.asExpr() - or n2.asExpr().(CastExpr).getExpr() = n1.asExpr() or n2.asExpr().(ConditionalExpr).getTrueExpr() = n1.asExpr() diff --git a/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll index c0740a43364..4d97bfdc3cc 100644 --- a/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll @@ -98,8 +98,6 @@ private predicate step(Node n1, Node n2) { n2.asExpr().(FieldRead).getField() = f ) or - n2.asExpr().(ParExpr).getExpr() = n1.asExpr() - or n2.asExpr().(CastExpr).getExpr() = n1.asExpr() or n2.asExpr().(ConditionalExpr).getTrueExpr() = n1.asExpr() diff --git a/java/ql/src/semmle/code/java/frameworks/Assertions.qll b/java/ql/src/semmle/code/java/frameworks/Assertions.qll index eb210d5e0d7..810af5ca41e 100644 --- a/java/ql/src/semmle/code/java/frameworks/Assertions.qll +++ b/java/ql/src/semmle/code/java/frameworks/Assertions.qll @@ -109,6 +109,6 @@ predicate assertFail(BasicBlock bb, ControlFlowNode n) { n = m.getACheck(any(BooleanLiteral b | b.getBooleanValue() = true)) ) or exists(AssertFailMethod m | n = m.getACheck()) or - n.(AssertStmt).getExpr().getProperExpr().(BooleanLiteral).getBooleanValue() = false + n.(AssertStmt).getExpr().(BooleanLiteral).getBooleanValue() = false ) } diff --git a/java/ql/test/library-tests/guards/guardslogic.ql b/java/ql/test/library-tests/guards/guardslogic.ql index 516a8670473..afbb313d664 100644 --- a/java/ql/test/library-tests/guards/guardslogic.ql +++ b/java/ql/test/library-tests/guards/guardslogic.ql @@ -4,6 +4,5 @@ import semmle.code.java.controlflow.Guards from Guard g, BasicBlock bb, boolean branch where g.controls(bb, branch) and - not g instanceof ParExpr and g.getEnclosingCallable().getDeclaringType().hasName("Logic") select g, branch, bb From 843fd37c7576a48c9bed533c4e6949cf79e6d6fc Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 29 Jan 2020 15:55:36 +0100 Subject: [PATCH 089/148] Java: Add change note. --- change-notes/1.24/analysis-java.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/change-notes/1.24/analysis-java.md b/change-notes/1.24/analysis-java.md index cfdd157d13f..b618d367a1e 100644 --- a/change-notes/1.24/analysis-java.md +++ b/change-notes/1.24/analysis-java.md @@ -32,3 +32,6 @@ The following changes in version 1.24 affect Java analysis in all applications. general file classification mechanism and thus suppression of alerts, and also any security queries using taint tracking, as test classes act as default barriers stopping taint flow. +* Parentheses are now no longer modelled directly in the AST, that is, the + `ParExpr` class is empty. Instead, a parenthesized expression can be + identified with the `Expr.isParenthesized()` member predicate. From d8b842298c797ce77b8892284633456b82ffa888 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 30 Jan 2020 10:54:54 +0100 Subject: [PATCH 090/148] Java: Autoformat. --- java/ql/src/Language Abuse/UselessUpcast.ql | 4 +--- .../Arithmetic/ConstantExpAppearsNonConstant.ql | 8 ++------ java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql | 4 +--- java/ql/src/semmle/code/java/Variable.qll | 4 +--- 4 files changed, 5 insertions(+), 15 deletions(-) diff --git a/java/ql/src/Language Abuse/UselessUpcast.ql b/java/ql/src/Language Abuse/UselessUpcast.ql index 18584da159c..d8b5761c909 100644 --- a/java/ql/src/Language Abuse/UselessUpcast.ql +++ b/java/ql/src/Language Abuse/UselessUpcast.ql @@ -36,9 +36,7 @@ predicate usefulUpcast(CastExpr e) { ) or // Upcasts that are performed on an operand of a ternary expression. - exists(ConditionalExpr ce | - e = ce.getTrueExpr() or e = ce.getFalseExpr() - ) + exists(ConditionalExpr ce | e = ce.getTrueExpr() or e = ce.getFalseExpr()) or // Upcasts to raw types. e.getType() instanceof RawType diff --git a/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql index eb4e86e65c9..90471c9d0c5 100644 --- a/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql +++ b/java/ql/src/Likely Bugs/Arithmetic/ConstantExpAppearsNonConstant.ql @@ -43,13 +43,9 @@ predicate isConstantExp(Expr e) { or exists(AndBitwiseExpr a | a = e | eval(a.getAnOperand()) = 0) or - exists(AndLogicalExpr a | a = e | - a.getAnOperand().(BooleanLiteral).getBooleanValue() = false - ) + exists(AndLogicalExpr a | a = e | a.getAnOperand().(BooleanLiteral).getBooleanValue() = false) or - exists(OrLogicalExpr o | o = e | - o.getAnOperand().(BooleanLiteral).getBooleanValue() = true - ) + exists(OrLogicalExpr o | o = e | o.getAnOperand().(BooleanLiteral).getBooleanValue() = true) } from Expr e diff --git a/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql b/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql index cbbc07f475c..8f91ae89211 100644 --- a/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql +++ b/java/ql/src/Likely Bugs/Concurrency/SynchSetUnsynchGet.ql @@ -22,9 +22,7 @@ import java * (for static methods) or a `synchronized(this){...}` block (for instance methods). */ predicate isSynchronizedByBlock(Method m) { - exists(SynchronizedStmt sync, Expr on | - sync = m.getBody().getAChild*() and on = sync.getExpr() - | + exists(SynchronizedStmt sync, Expr on | sync = m.getBody().getAChild*() and on = sync.getExpr() | if m.isStatic() then on.(TypeLiteral).getTypeName().getType() = m.getDeclaringType() else on.(ThisAccess).getType().(RefType).getSourceDeclaration() = m.getDeclaringType() diff --git a/java/ql/src/semmle/code/java/Variable.qll b/java/ql/src/semmle/code/java/Variable.qll index d63bcc6fe1c..44c7e617d3f 100755 --- a/java/ql/src/semmle/code/java/Variable.qll +++ b/java/ql/src/semmle/code/java/Variable.qll @@ -16,9 +16,7 @@ class Variable extends @variable, Annotatable, Element, Modifiable { Expr getAnAssignedValue() { exists(LocalVariableDeclExpr e | e.getVariable() = this and result = e.getInit()) or - exists(AssignExpr e | - e.getDest() = this.getAnAccess() and result = e.getSource() - ) + exists(AssignExpr e | e.getDest() = this.getAnAccess() and result = e.getSource()) } /** Gets the initializer expression of this variable. */ From ea3d7b1b2f24ae00a3bb070ec831218a5865ec6a Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 30 Jan 2020 11:07:03 +0100 Subject: [PATCH 091/148] Java: Adjust stubs and unit test. --- .../security/CWE-090/LdapInjection.expected | 32 +++++++++---------- .../security/CWE-090/LdapInjection.java | 10 +++--- .../client/api/LdapNetworkConnection.java | 9 +++++- .../owasp/esapi/reference/DefaultEncoder.java | 2 +- .../ldap/filter/HardcodedFilter.java | 1 + .../ldap/support/LdapEncoder.java | 2 +- .../com/unboundid/ldap/sdk/Filter.java | 2 ++ 7 files changed, 34 insertions(+), 24 deletions(-) diff --git a/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected b/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected index e07c4ff05fd..c275cda2e58 100644 --- a/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected +++ b/java/ql/test/query-tests/security/CWE-090/LdapInjection.expected @@ -27,6 +27,7 @@ edges | LdapInjection.java:127:31:127:73 | uBadSearchRequestAsync : String | LdapInjection.java:131:19:131:19 | s | | LdapInjection.java:127:76:127:109 | uBadSRDNAsync : String | LdapInjection.java:131:19:131:19 | s | | LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT : String | LdapInjection.java:135:58:135:115 | createNOTFilter(...) | +| LdapInjection.java:138:31:138:75 | uBadFilterCreateToString : String | LdapInjection.java:139:58:139:107 | toString(...) | | LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer : String | LdapInjection.java:145:58:145:69 | toString(...) | | LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate : String | LdapInjection.java:152:14:152:26 | duplicate(...) | | LdapInjection.java:155:32:155:80 | uBadROSearchRequestDuplicate : String | LdapInjection.java:159:14:159:26 | duplicate(...) | @@ -47,18 +48,15 @@ edges | LdapInjection.java:230:30:230:74 | sBadLdapQueryWithFilter2 : String | LdapInjection.java:232:24:232:57 | filter(...) | | LdapInjection.java:235:31:235:68 | sBadLdapQueryBase : String | LdapInjection.java:236:12:236:66 | base(...) | | LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex : String | LdapInjection.java:240:24:240:98 | is(...) | +| LdapInjection.java:243:31:243:69 | sBadFilterToString : String | LdapInjection.java:244:18:244:83 | toString(...) | | LdapInjection.java:247:31:247:67 | sBadFilterEncode : String | LdapInjection.java:250:18:250:29 | toString(...) | | LdapInjection.java:266:30:266:54 | aBad : String | LdapInjection.java:268:36:268:55 | ... + ... | | LdapInjection.java:266:57:266:83 | aBadDN : String | LdapInjection.java:268:14:268:33 | ... + ... | +| LdapInjection.java:271:30:271:54 | aBad : String | LdapInjection.java:273:65:273:84 | ... + ... | +| LdapInjection.java:271:57:271:94 | aBadDNObjToString : String | LdapInjection.java:273:14:273:62 | getName(...) | | LdapInjection.java:276:30:276:67 | aBadSearchRequest : String | LdapInjection.java:280:14:280:14 | s | | LdapInjection.java:283:74:283:103 | aBadDNObj : String | LdapInjection.java:287:14:287:14 | s | | LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet : String | LdapInjection.java:294:14:294:24 | getBase(...) | -| LdapInjection.java:312:23:312:58 | okEncodeForLDAP : String | LdapInjection.java:314:61:314:75 | okEncodeForLDAP : String | -| LdapInjection.java:314:39:314:76 | encodeForLDAP(...) : String | LdapInjection.java:314:29:314:82 | ... + ... | -| LdapInjection.java:314:61:314:75 | okEncodeForLDAP : String | LdapInjection.java:314:39:314:76 | encodeForLDAP(...) : String | -| LdapInjection.java:318:23:318:57 | okFilterEncode : String | LdapInjection.java:319:64:319:77 | okFilterEncode : String | -| LdapInjection.java:319:39:319:78 | filterEncode(...) : String | LdapInjection.java:319:29:319:84 | ... + ... | -| LdapInjection.java:319:64:319:77 | okFilterEncode : String | LdapInjection.java:319:39:319:78 | filterEncode(...) : String | nodes | LdapInjection.java:41:28:41:52 | jBad : String | semmle.label | jBad : String | | LdapInjection.java:41:55:41:81 | jBadDN : String | semmle.label | jBadDN : String | @@ -112,6 +110,8 @@ nodes | LdapInjection.java:131:19:131:19 | s | semmle.label | s | | LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT : String | semmle.label | uBadFilterCreateNOT : String | | LdapInjection.java:135:58:135:115 | createNOTFilter(...) | semmle.label | createNOTFilter(...) | +| LdapInjection.java:138:31:138:75 | uBadFilterCreateToString : String | semmle.label | uBadFilterCreateToString : String | +| LdapInjection.java:139:58:139:107 | toString(...) | semmle.label | toString(...) | | LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer : String | semmle.label | uBadFilterCreateToStringBuffer : String | | LdapInjection.java:145:58:145:69 | toString(...) | semmle.label | toString(...) | | LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate : String | semmle.label | uBadSearchRequestDuplicate : String | @@ -152,26 +152,24 @@ nodes | LdapInjection.java:236:12:236:66 | base(...) | semmle.label | base(...) | | LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex : String | semmle.label | sBadLdapQueryComplex : String | | LdapInjection.java:240:24:240:98 | is(...) | semmle.label | is(...) | +| LdapInjection.java:243:31:243:69 | sBadFilterToString : String | semmle.label | sBadFilterToString : String | +| LdapInjection.java:244:18:244:83 | toString(...) | semmle.label | toString(...) | | LdapInjection.java:247:31:247:67 | sBadFilterEncode : String | semmle.label | sBadFilterEncode : String | | LdapInjection.java:250:18:250:29 | toString(...) | semmle.label | toString(...) | | LdapInjection.java:266:30:266:54 | aBad : String | semmle.label | aBad : String | | LdapInjection.java:266:57:266:83 | aBadDN : String | semmle.label | aBadDN : String | | LdapInjection.java:268:14:268:33 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:268:36:268:55 | ... + ... | semmle.label | ... + ... | +| LdapInjection.java:271:30:271:54 | aBad : String | semmle.label | aBad : String | +| LdapInjection.java:271:57:271:94 | aBadDNObjToString : String | semmle.label | aBadDNObjToString : String | +| LdapInjection.java:273:14:273:62 | getName(...) | semmle.label | getName(...) | +| LdapInjection.java:273:65:273:84 | ... + ... | semmle.label | ... + ... | | LdapInjection.java:276:30:276:67 | aBadSearchRequest : String | semmle.label | aBadSearchRequest : String | | LdapInjection.java:280:14:280:14 | s | semmle.label | s | | LdapInjection.java:283:74:283:103 | aBadDNObj : String | semmle.label | aBadDNObj : String | | LdapInjection.java:287:14:287:14 | s | semmle.label | s | | LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet : String | semmle.label | aBadDNSearchRequestGet : String | | LdapInjection.java:294:14:294:24 | getBase(...) | semmle.label | getBase(...) | -| LdapInjection.java:312:23:312:58 | okEncodeForLDAP : String | semmle.label | okEncodeForLDAP : String | -| LdapInjection.java:314:29:314:82 | ... + ... | semmle.label | ... + ... | -| LdapInjection.java:314:39:314:76 | encodeForLDAP(...) : String | semmle.label | encodeForLDAP(...) : String | -| LdapInjection.java:314:61:314:75 | okEncodeForLDAP : String | semmle.label | okEncodeForLDAP : String | -| LdapInjection.java:318:23:318:57 | okFilterEncode : String | semmle.label | okFilterEncode : String | -| LdapInjection.java:319:29:319:84 | ... + ... | semmle.label | ... + ... | -| LdapInjection.java:319:39:319:78 | filterEncode(...) : String | semmle.label | filterEncode(...) : String | -| LdapInjection.java:319:64:319:77 | okFilterEncode : String | semmle.label | okFilterEncode : String | #select | LdapInjection.java:43:16:43:35 | ... + ... | LdapInjection.java:41:55:41:81 | jBadDN : String | LdapInjection.java:43:16:43:35 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:41:55:41:81 | jBadDN | this user input | | LdapInjection.java:43:38:43:57 | ... + ... | LdapInjection.java:41:28:41:52 | jBad : String | LdapInjection.java:43:38:43:57 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:41:28:41:52 | jBad | this user input | @@ -201,6 +199,7 @@ nodes | LdapInjection.java:131:19:131:19 | s | LdapInjection.java:127:31:127:73 | uBadSearchRequestAsync : String | LdapInjection.java:131:19:131:19 | s | LDAP query might include code from $@. | LdapInjection.java:127:31:127:73 | uBadSearchRequestAsync | this user input | | LdapInjection.java:131:19:131:19 | s | LdapInjection.java:127:76:127:109 | uBadSRDNAsync : String | LdapInjection.java:131:19:131:19 | s | LDAP query might include code from $@. | LdapInjection.java:127:76:127:109 | uBadSRDNAsync | this user input | | LdapInjection.java:135:58:135:115 | createNOTFilter(...) | LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT : String | LdapInjection.java:135:58:135:115 | createNOTFilter(...) | LDAP query might include code from $@. | LdapInjection.java:134:31:134:70 | uBadFilterCreateNOT | this user input | +| LdapInjection.java:139:58:139:107 | toString(...) | LdapInjection.java:138:31:138:75 | uBadFilterCreateToString : String | LdapInjection.java:139:58:139:107 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:138:31:138:75 | uBadFilterCreateToString | this user input | | LdapInjection.java:145:58:145:69 | toString(...) | LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer : String | LdapInjection.java:145:58:145:69 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:142:32:142:82 | uBadFilterCreateToStringBuffer | this user input | | LdapInjection.java:152:14:152:26 | duplicate(...) | LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate : String | LdapInjection.java:152:14:152:26 | duplicate(...) | LDAP query might include code from $@. | LdapInjection.java:148:32:148:78 | uBadSearchRequestDuplicate | this user input | | LdapInjection.java:159:14:159:26 | duplicate(...) | LdapInjection.java:155:32:155:80 | uBadROSearchRequestDuplicate : String | LdapInjection.java:159:14:159:26 | duplicate(...) | LDAP query might include code from $@. | LdapInjection.java:155:32:155:80 | uBadROSearchRequestDuplicate | this user input | @@ -221,11 +220,12 @@ nodes | LdapInjection.java:232:24:232:57 | filter(...) | LdapInjection.java:230:30:230:74 | sBadLdapQueryWithFilter2 : String | LdapInjection.java:232:24:232:57 | filter(...) | LDAP query might include code from $@. | LdapInjection.java:230:30:230:74 | sBadLdapQueryWithFilter2 | this user input | | LdapInjection.java:236:12:236:66 | base(...) | LdapInjection.java:235:31:235:68 | sBadLdapQueryBase : String | LdapInjection.java:236:12:236:66 | base(...) | LDAP query might include code from $@. | LdapInjection.java:235:31:235:68 | sBadLdapQueryBase | this user input | | LdapInjection.java:240:24:240:98 | is(...) | LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex : String | LdapInjection.java:240:24:240:98 | is(...) | LDAP query might include code from $@. | LdapInjection.java:239:31:239:71 | sBadLdapQueryComplex | this user input | +| LdapInjection.java:244:18:244:83 | toString(...) | LdapInjection.java:243:31:243:69 | sBadFilterToString : String | LdapInjection.java:244:18:244:83 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:243:31:243:69 | sBadFilterToString | this user input | | LdapInjection.java:250:18:250:29 | toString(...) | LdapInjection.java:247:31:247:67 | sBadFilterEncode : String | LdapInjection.java:250:18:250:29 | toString(...) | LDAP query might include code from $@. | LdapInjection.java:247:31:247:67 | sBadFilterEncode | this user input | | LdapInjection.java:268:14:268:33 | ... + ... | LdapInjection.java:266:57:266:83 | aBadDN : String | LdapInjection.java:268:14:268:33 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:266:57:266:83 | aBadDN | this user input | | LdapInjection.java:268:36:268:55 | ... + ... | LdapInjection.java:266:30:266:54 | aBad : String | LdapInjection.java:268:36:268:55 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:266:30:266:54 | aBad | this user input | +| LdapInjection.java:273:14:273:62 | getName(...) | LdapInjection.java:271:57:271:94 | aBadDNObjToString : String | LdapInjection.java:273:14:273:62 | getName(...) | LDAP query might include code from $@. | LdapInjection.java:271:57:271:94 | aBadDNObjToString | this user input | +| LdapInjection.java:273:65:273:84 | ... + ... | LdapInjection.java:271:30:271:54 | aBad : String | LdapInjection.java:273:65:273:84 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:271:30:271:54 | aBad | this user input | | LdapInjection.java:280:14:280:14 | s | LdapInjection.java:276:30:276:67 | aBadSearchRequest : String | LdapInjection.java:280:14:280:14 | s | LDAP query might include code from $@. | LdapInjection.java:276:30:276:67 | aBadSearchRequest | this user input | | LdapInjection.java:287:14:287:14 | s | LdapInjection.java:283:74:283:103 | aBadDNObj : String | LdapInjection.java:287:14:287:14 | s | LDAP query might include code from $@. | LdapInjection.java:283:74:283:103 | aBadDNObj | this user input | | LdapInjection.java:294:14:294:24 | getBase(...) | LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet : String | LdapInjection.java:294:14:294:24 | getBase(...) | LDAP query might include code from $@. | LdapInjection.java:290:30:290:72 | aBadDNSearchRequestGet | this user input | -| LdapInjection.java:314:29:314:82 | ... + ... | LdapInjection.java:312:23:312:58 | okEncodeForLDAP : String | LdapInjection.java:314:29:314:82 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:312:23:312:58 | okEncodeForLDAP | this user input | -| LdapInjection.java:319:29:319:84 | ... + ... | LdapInjection.java:318:23:318:57 | okFilterEncode : String | LdapInjection.java:319:29:319:84 | ... + ... | LDAP query might include code from $@. | LdapInjection.java:318:23:318:57 | okFilterEncode | this user input | diff --git a/java/ql/test/query-tests/security/CWE-090/LdapInjection.java b/java/ql/test/query-tests/security/CWE-090/LdapInjection.java index 1680664d872..5a5f7b238b4 100644 --- a/java/ql/test/query-tests/security/CWE-090/LdapInjection.java +++ b/java/ql/test/query-tests/security/CWE-090/LdapInjection.java @@ -136,7 +136,7 @@ public class LdapInjection { } public void testUnboundBad9(@RequestParam String uBadFilterCreateToString, LDAPConnection c) throws LDAPException { - c.search(null, "ou=system", null, null, 1, 1, false, Filter.create(uBadFilterCreateToString).toString()); // False Negative + c.search(null, "ou=system", null, null, 1, 1, false, Filter.create(uBadFilterCreateToString).toString()); } public void testUnboundBad10(@RequestParam String uBadFilterCreateToStringBuffer, LDAPConnection c) throws LDAPException { @@ -241,7 +241,7 @@ public class LdapInjection { } public void testSpringBad12(@RequestParam String sBadFilterToString, LdapTemplate c) { - c.search("", new HardcodedFilter("(uid=" + sBadFilterToString + ")").toString(), 1, false, null); // False Negative + c.search("", new HardcodedFilter("(uid=" + sBadFilterToString + ")").toString(), 1, false, null); } public void testSpringBad13(@RequestParam String sBadFilterEncode, LdapTemplate c) { @@ -270,7 +270,7 @@ public class LdapInjection { public void testApacheBad2(@RequestParam String aBad, @RequestParam String aBadDNObjToString, LdapNetworkConnection c) throws LdapException { - c.search(new Dn("ou=system" + aBadDNObjToString).getName(), "(uid=" + aBad + ")", null); // False Negative + c.search(new Dn("ou=system" + aBadDNObjToString).getName(), "(uid=" + aBad + ")", null); } public void testApacheBad3(@RequestParam String aBadSearchRequest, LdapConnection c) @@ -311,12 +311,12 @@ public class LdapInjection { // ESAPI encoder sanitizer public void testOk3(@RequestParam String okEncodeForLDAP, DirContext ctx) throws NamingException { Encoder encoder = DefaultEncoder.getInstance(); - ctx.search("ou=system", "(uid=" + encoder.encodeForLDAP(okEncodeForLDAP) + ")", new SearchControls()); // False Positive + ctx.search("ou=system", "(uid=" + encoder.encodeForLDAP(okEncodeForLDAP) + ")", new SearchControls()); } // Spring LdapEncoder sanitizer public void testOk4(@RequestParam String okFilterEncode, DirContext ctx) throws NamingException { - ctx.search("ou=system", "(uid=" + LdapEncoder.filterEncode(okFilterEncode) + ")", new SearchControls()); // False Positive + ctx.search("ou=system", "(uid=" + LdapEncoder.filterEncode(okFilterEncode) + ")", new SearchControls()); } // UnboundID Filter.encodeValue sanitizer diff --git a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapNetworkConnection.java b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapNetworkConnection.java index cc40586fc43..4de173ac1a7 100644 --- a/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapNetworkConnection.java +++ b/java/ql/test/stubs/apache-ldap-1.0.2/org/apache/directory/ldap/client/api/LdapNetworkConnection.java @@ -2,8 +2,15 @@ package org.apache.directory.ldap.client.api; import org.apache.directory.api.ldap.model.exception.LdapException; import org.apache.directory.api.ldap.model.cursor.EntryCursor; +import org.apache.directory.api.ldap.model.cursor.SearchCursor; +import org.apache.directory.api.ldap.model.message.SearchRequest; import org.apache.directory.api.ldap.model.message.SearchScope; +import org.apache.directory.api.ldap.model.name.Dn; + +public class LdapNetworkConnection implements LdapConnection { + public SearchCursor search(SearchRequest searchRequest) throws LdapException { return null; } -public class LdapNetworkConnection /*implements LdapConnection*/ { public EntryCursor search(String baseDn, String filter, SearchScope scope, String... attributes) throws LdapException { return null; } + + public EntryCursor search(Dn baseDn, String filter, SearchScope scope, String... attributes) throws LdapException { return null; } } diff --git a/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java b/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java index 9f93a3f67c5..8a1169f073a 100644 --- a/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java +++ b/java/ql/test/stubs/esapi-2.0.1/org/owasp/esapi/reference/DefaultEncoder.java @@ -4,5 +4,5 @@ import org.owasp.esapi.Encoder; public class DefaultEncoder implements Encoder { public static Encoder getInstance() { return null; } - public String encodeForLDAP(String input) { return input; } + public String encodeForLDAP(String input) { return null; } } diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/HardcodedFilter.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/HardcodedFilter.java index b9cfa6dd5ce..bc43dddc6f8 100644 --- a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/HardcodedFilter.java +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/filter/HardcodedFilter.java @@ -3,4 +3,5 @@ package org.springframework.ldap.filter; public class HardcodedFilter implements Filter { public HardcodedFilter(String filter) { } public StringBuffer encode(StringBuffer buff) { return buff; } + public String toString() { return ""; } } diff --git a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapEncoder.java b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapEncoder.java index ea234029338..a85d74192b3 100644 --- a/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapEncoder.java +++ b/java/ql/test/stubs/spring-ldap-2.3.2/org/springframework/ldap/support/LdapEncoder.java @@ -1,5 +1,5 @@ package org.springframework.ldap.support; public class LdapEncoder { - public static String filterEncode(String value) { return value; } + public static String filterEncode(String value) { return null; } } diff --git a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/Filter.java b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/Filter.java index 2f24ed8c57a..1dc7c823759 100644 --- a/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/Filter.java +++ b/java/ql/test/stubs/unboundid-ldap-4.0.14/com/unboundid/ldap/sdk/Filter.java @@ -10,4 +10,6 @@ public class Filter { public static java.lang.String encodeValue(java.lang.String value) { return null; } public void toNormalizedString(java.lang.StringBuilder buffer) { } + + public String toString() { return ""; } } From 148e87c61d4201802c7aa38e1b697d1bd99eafa7 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Thu, 30 Jan 2020 11:37:03 +0100 Subject: [PATCH 092/148] C++: Put AliasedSSA.qll in new qlformat style --- .../implementation/aliased_ssa/internal/AliasedSSA.qll | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll index 1843ad5815c..bcf3e5db19e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll @@ -554,8 +554,9 @@ MemoryLocation getResultMemoryLocation(Instruction instr) { ) or kind instanceof EntireAllocationMemoryAccess and - result = TEntireAllocationMemoryLocation(getAddressOperandAllocation(instr - .getResultAddressOperand()), isMayAccess) + result = + TEntireAllocationMemoryLocation(getAddressOperandAllocation(instr.getResultAddressOperand()), + isMayAccess) or kind instanceof EscapedMemoryAccess and result = TAllAliasedMemory(instr.getEnclosingIRFunction(), isMayAccess) @@ -584,8 +585,9 @@ MemoryLocation getOperandMemoryLocation(MemoryOperand operand) { ) or kind instanceof EntireAllocationMemoryAccess and - result = TEntireAllocationMemoryLocation(getAddressOperandAllocation(operand - .getAddressOperand()), isMayAccess) + result = + TEntireAllocationMemoryLocation(getAddressOperandAllocation(operand.getAddressOperand()), + isMayAccess) or kind instanceof EscapedMemoryAccess and result = TAllAliasedMemory(operand.getEnclosingIRFunction(), isMayAccess) From a1675775515e9ea1294a507b2c4e8063b16d9bea Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 30 Jan 2020 12:01:36 +0100 Subject: [PATCH 093/148] Java: Add java.lang.Number as a sanitizer for SQL injection. --- java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll | 4 +++- java/ql/src/semmle/code/java/JDK.qll | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll b/java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll index 3d2e2b91434..2572d91cb99 100644 --- a/java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll +++ b/java/ql/src/Security/CWE/CWE-089/SqlInjectionLib.qll @@ -54,7 +54,9 @@ private class QueryInjectionFlowConfig extends TaintTracking::Configuration { override predicate isSink(DataFlow::Node sink) { sink instanceof QueryInjectionSink } override predicate isSanitizer(DataFlow::Node node) { - node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType + node.getType() instanceof PrimitiveType or + node.getType() instanceof BoxedType or + node.getType() instanceof NumberType } } diff --git a/java/ql/src/semmle/code/java/JDK.qll b/java/ql/src/semmle/code/java/JDK.qll index 37f8f359c80..d9a1a15e5d3 100644 --- a/java/ql/src/semmle/code/java/JDK.qll +++ b/java/ql/src/semmle/code/java/JDK.qll @@ -101,6 +101,16 @@ class TypeMath extends Class { TypeMath() { this.hasQualifiedName("java.lang", "Math") } } +/** The class `java.lang.Number`. */ +class TypeNumber extends RefType { + TypeNumber() { this.hasQualifiedName("java.lang", "Number") } +} + +/** A (reflexive, transitive) subtype of `java.lang.Number`. */ +class NumberType extends RefType { + NumberType() { exists(TypeNumber number | hasSubtype*(number, this)) } +} + /** A numeric type, including both primitive and boxed types. */ class NumericType extends Type { NumericType() { From 141d4bfb70d0073c02ec197dfba1d95f92305682 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 30 Jan 2020 12:26:02 +0000 Subject: [PATCH 094/148] TS: Handle multiple slashes in scope name --- .../extractor/lib/typescript/src/common.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/javascript/extractor/lib/typescript/src/common.ts b/javascript/extractor/lib/typescript/src/common.ts index 781d61e388b..1dbe54b4632 100644 --- a/javascript/extractor/lib/typescript/src/common.ts +++ b/javascript/extractor/lib/typescript/src/common.ts @@ -6,9 +6,19 @@ import { VirtualSourceRoot } from "./virtual_source_root"; /** * Extracts the package name from the prefix of an import string. */ -const packageNameRex = /^(?:@[\w.-]+[/\\])?\w[\w.-]*(?=[/\\]|$)/; +const packageNameRex = /^(?:@[\w.-]+[/\\]+)?\w[\w.-]*(?=[/\\]|$)/; const extensions = ['.ts', '.tsx', '.d.ts', '.js', '.jsx']; +function getPackageName(importString: string) { + let packageNameMatch = packageNameRex.exec(importString); + if (packageNameMatch == null) return null; + let packageName = packageNameMatch[0]; + if (packageName.charAt(0) === '@') { + packageName = packageName.replace(/[/\\]+/g, '/'); // Normalize slash after the scope. + } + return packageName; +} + export class Project { public program: ts.Program = null; private host: ts.CompilerHost; @@ -75,9 +85,8 @@ export class Project { */ private redirectModuleName(moduleName: string, containingFile: string, options: ts.CompilerOptions): ts.ResolvedModule { // Get a package name from the leading part of the module name, e.g. '@scope/foo' from '@scope/foo/bar'. - let packageNameMatch = packageNameRex.exec(moduleName); - if (packageNameMatch == null) return null; - let packageName = packageNameMatch[0]; + let packageName = getPackageName(moduleName); + if (packageName == null) return null; // Get the overridden location of this package, if one exists. let packageEntryPoint = this.packageEntryPoints.get(packageName); From 92dbfb28588c6ef1b61aa3f02468782dd05fdcee Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 30 Jan 2020 12:31:25 +0000 Subject: [PATCH 095/148] JS: Handle LGTM_WORKSPACE and fix emptiness check --- .../js/extractor/EnvironmentVariables.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java b/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java index 7c5bac5a9f5..6f8e7124b3c 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java +++ b/javascript/extractor/src/com/semmle/js/extractor/EnvironmentVariables.java @@ -11,15 +11,18 @@ public class EnvironmentVariables { public static final String CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR_ENV_VAR = "CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR"; + public static final String LGTM_WORKSPACE_ENV_VAR = + "LGTM_WORKSPACE"; + /** * Gets the extractor root based on the CODEQL_EXTRACTOR_JAVASCRIPT_ROOT or * SEMMLE_DIST or environment variable, or null if neither is set. */ public static String tryGetExtractorRoot() { - String env = Env.systemEnv().get(CODEQL_EXTRACTOR_JAVASCRIPT_ROOT_ENV_VAR); - if (env != null && !env.isEmpty()) return env; - env = Env.systemEnv().get(Var.SEMMLE_DIST); - if (env != null && !env.isEmpty()) return env; + String env = Env.systemEnv().getNonEmpty(CODEQL_EXTRACTOR_JAVASCRIPT_ROOT_ENV_VAR); + if (env != null) return env; + env = Env.systemEnv().getNonEmpty(Var.SEMMLE_DIST); + if (env != null) return env; return null; } @@ -35,11 +38,15 @@ public class EnvironmentVariables { return env; } + /** + * Gets the scratch directory from the appropriate environment variable. + */ public static String getScratchDir() { - String env = Env.systemEnv().get(CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR_ENV_VAR); - if (env == null) { - throw new UserError(CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR_ENV_VAR + " must be set"); - } - return env; + String env = Env.systemEnv().getNonEmpty(CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR_ENV_VAR); + if (env != null) return env; + env = Env.systemEnv().getNonEmpty(LGTM_WORKSPACE_ENV_VAR); + if (env != null) return env; + + throw new UserError(CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR_ENV_VAR + " or " + LGTM_WORKSPACE_ENV_VAR + " must be set"); } } From 1bf81650982f545c1d2fa071d93a7b3bc7a67af5 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 30 Jan 2020 12:41:02 +0000 Subject: [PATCH 096/148] TS: Other review comments --- javascript/extractor/lib/typescript/src/main.ts | 2 +- .../lib/typescript/src/virtual_source_root.ts | 3 ++- .../src/com/semmle/js/extractor/AutoBuild.java | 10 +++++----- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index 16c1dbd1e5e..246a59fb4d1 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -318,7 +318,7 @@ function handleOpenProjectCommand(command: OpenProjectCommand) { console.warn('TypeScript: reported ' + diagnostics.length + ' semantic errors.'); for (let diagnostic of diagnostics) { let text = diagnostic.messageText; - if (typeof text !== 'string') { + if (text && typeof text !== 'string') { text = text.messageText; } let locationStr = ''; diff --git a/javascript/extractor/lib/typescript/src/virtual_source_root.ts b/javascript/extractor/lib/typescript/src/virtual_source_root.ts index 43f01455650..88f9228790c 100644 --- a/javascript/extractor/lib/typescript/src/virtual_source_root.ts +++ b/javascript/extractor/lib/typescript/src/virtual_source_root.ts @@ -2,7 +2,8 @@ import * as pathlib from "path"; import * as ts from "./typescript"; /** - * Mapping from the source root to the virtual source root. + * Mapping from the real source root to the virtual source root, + * a directory whose folder structure mirrors the real source root, but with `node_modules` installed. */ export class VirtualSourceRoot { constructor( diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index b4b949c52c0..40a3a531d01 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -709,12 +709,12 @@ public class AutoBuild { propsToRemove.add(packageName); } else { // Remove file dependency on a package that don't exist in the checkout. - String dependecy = getChildAsString(dependencyObj, packageName); - if (dependecy != null && (dependecy.startsWith("file:") || dependecy.startsWith("./") || dependecy.startsWith("../"))) { - if (dependecy.startsWith("file:")) { - dependecy = dependecy.substring("file:".length()); + String dependency = getChildAsString(dependencyObj, packageName); + if (dependency != null && (dependency.startsWith("file:") || dependency.startsWith("./") || dependency.startsWith("../"))) { + if (dependency.startsWith("file:")) { + dependency = dependency.substring("file:".length()); } - Path resolvedPackage = path.getParent().resolve(dependecy + "/package.json"); + Path resolvedPackage = path.getParent().resolve(dependency + "/package.json"); if (!Files.exists(resolvedPackage)) { propsToRemove.add(packageName); } From b88cc50cdb922dd337c3d2ddf7e44bb0882bac94 Mon Sep 17 00:00:00 2001 From: Asger F Date: Thu, 30 Jan 2020 12:42:58 +0000 Subject: [PATCH 097/148] Apply suggestions from code review Co-Authored-By: Max Schaefer <54907921+max-schaefer@users.noreply.github.com> --- .../extractor/lib/typescript/src/virtual_source_root.ts | 4 ++-- .../extractor/src/com/semmle/js/extractor/AutoBuild.java | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/javascript/extractor/lib/typescript/src/virtual_source_root.ts b/javascript/extractor/lib/typescript/src/virtual_source_root.ts index 88f9228790c..5f1bbf2b95e 100644 --- a/javascript/extractor/lib/typescript/src/virtual_source_root.ts +++ b/javascript/extractor/lib/typescript/src/virtual_source_root.ts @@ -17,7 +17,7 @@ export class VirtualSourceRoot { ) {} /** - * Maps a path under the real source root to the corresonding path in the virtual source root. + * Maps a path under the real source root to the corresponding path in the virtual source root. */ public toVirtualPath(path: string) { if (!this.virtualSourceRoot) return null; @@ -27,7 +27,7 @@ export class VirtualSourceRoot { } /** - * Maps a path under the real source root to the corresonding path in the virtual source root. + * Maps a path under the real source root to the corresponding path in the virtual source root. */ public toVirtualPathIfFileExists(path: string) { let virtualPath = this.toVirtualPath(path); diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index 40a3a531d01..d3114360c7e 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -397,7 +397,7 @@ public class AutoBuild { for (FileType filetype : defaultExtract) for (String extension : filetype.getExtensions()) patterns.add("**/*" + extension); - // include .eslintrc files and package.json files + // include .eslintrc files, package.json files, and tsconfig.json files patterns.add("**/.eslintrc*"); patterns.add("**/package.json"); patterns.add("**/tsconfig.json"); @@ -601,7 +601,7 @@ public class AutoBuild { } /** - * Returns an existing file named dir/stem.ext where ext is any + * Returns an existing file named dir/stem.ext where .ext is any * of the given extensions, or null if no such file exists. */ private static Path tryResolveWithExtensions(Path dir, String stem, Iterable extensions) { @@ -708,7 +708,7 @@ public class AutoBuild { // Remove dependency on local package propsToRemove.add(packageName); } else { - // Remove file dependency on a package that don't exist in the checkout. + // Remove file dependency on a package that doesn't exist in the checkout. String dependency = getChildAsString(dependencyObj, packageName); if (dependency != null && (dependency.startsWith("file:") || dependency.startsWith("./") || dependency.startsWith("../"))) { if (dependency.startsWith("file:")) { From 162c19c348f008a74c88c82ccca006a099069aed Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 30 Jan 2020 14:04:04 +0100 Subject: [PATCH 098/148] changes based on review --- .../semmle/javascript/frameworks/Logging.qll | 2 +- .../security/dataflow/ExceptionXss.qll | 34 +++++++---------- .../javascript/security/dataflow/Xss.qll | 38 +++++++++---------- .../Security/CWE-079/ReflectedXss.expected | 5 +++ .../ReflectedXssWithCustomSanitizer.expected | 1 + .../Security/CWE-079/exception-xss.js | 32 +++++++++++++++- 6 files changed, 70 insertions(+), 42 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/Logging.qll b/javascript/ql/src/semmle/javascript/frameworks/Logging.qll index d8646575c4a..8fda6f8e1c1 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Logging.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Logging.qll @@ -17,7 +17,7 @@ abstract class LoggerCall extends DataFlow::CallNode { /** * Gets a log level name that is used in RFC5424, `npm`, `console`. */ -private string getAStandardLoggerMethodName() { +string getAStandardLoggerMethodName() { result = "crit" or result = "debug" or result = "error" or diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll index 1d4dabe8a8a..c64ecfd361d 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll @@ -16,27 +16,21 @@ module ExceptionXss { * Gets the name of a method that does not leak taint from its arguments if an exception is thrown by the method. */ private string getAnUnlikelyToThrowMethodName() { - result = "getElementById" or - result = "indexOf" or - result = "stringify" or - result = "assign" or - // fs methods. (The callback argument to the async functions are vulnerable, but its unlikely that the callback is the user-controlled part). - result = "existsSync" or - result = "exists" or - result = "writeFileSync" or - result = "writeFile" or - result = "appendFile" or - result = "appendFileSync" or - result = "pick" or - // log.info etc. - result = "info" or - result = "warn" or - result = "error" or - result = "join" or + result = "getElementById" or // document.getElementById + result = "indexOf" or // String.prototype.indexOf + result = "assign" or // Object.assign + result = "pick" or // _.pick + result = getAStandardLoggerMethodName() or // log.info etc. result = "val" or // $.val result = "parse" or // JSON.parse - result = "push" or // Array.prototype.push - result = "test" // RegExp.prototype.test + result = "stringify" or // JSON.stringify + result = "test" or // RegExp.prototype.test + result = "setItem" or // localStorage.setItem + result = "existsSync" or + // the "fs" methods are a mix of "this is safe" and "you have bigger problems". + exists(ExternalMemberDecl decl | decl.hasQualifiedName("fs", result)) or + // Array methods are generally exception safe. + exists(ExternalMemberDecl decl | decl.hasQualifiedName("Array", result)) } /** @@ -104,7 +98,7 @@ module ExceptionXss { } /** - * Get the parameter in the callback that contains an error. + * Gets the parameter in the callback that contains an error. * In the current implementation this is always the first parameter. */ DataFlow::Node getErrorParam() { result = errorParameter } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll index 6ff78fe24dd..badcf326f2c 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll @@ -51,24 +51,6 @@ module Shared { ) } } - - /** - * A property read from a safe property is considered a sanitizer. - */ - class SafePropertyReadSanitizer extends Sanitizer, DataFlow::Node { - SafePropertyReadSanitizer() { - exists(PropAccess pacc | pacc = this.asExpr() | - isSafeLocationProperty(pacc) - or - // `$(location.hash)` is a fairly common and safe idiom - // (because `location.hash` always starts with `#`), - // so we mark `hash` as safe for the purposes of this query - pacc.getPropertyName() = "hash" - or - pacc.getPropertyName() = "length" - ) - } - } } /** Provides classes and predicates for the DOM-based XSS query. */ @@ -277,6 +259,24 @@ module DomBasedXss { } } + /** + * A property read from a safe property is considered a sanitizer. + */ + class SafePropertyReadSanitizer extends Sanitizer, DataFlow::Node { + SafePropertyReadSanitizer() { + exists(PropAccess pacc | pacc = this.asExpr() | + isSafeLocationProperty(pacc) + or + // `$(location.hash)` is a fairly common and safe idiom + // (because `location.hash` always starts with `#`), + // so we mark `hash` as safe for the purposes of this query + pacc.getPropertyName() = "hash" + or + pacc.getPropertyName() = "length" + ) + } + } + /** * A regexp replacement involving an HTML meta-character, viewed as a sanitizer for * XSS vulnerabilities. @@ -287,8 +287,6 @@ module DomBasedXss { private class MetacharEscapeSanitizer extends Sanitizer, Shared::MetacharEscapeSanitizer { } private class UriEncodingSanitizer extends Sanitizer, Shared::UriEncodingSanitizer { } - - private class SafePropertyReadSanitizer extends Sanitizer, Shared::SafePropertyReadSanitizer {} } /** Provides classes and predicates for the reflected XSS query. */ diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected index bcf54b75580..e283a5676fa 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected @@ -9,6 +9,9 @@ nodes | etherpad.js:9:16:9:53 | req.que ... e + ")" | | etherpad.js:11:12:11:19 | response | | etherpad.js:11:12:11:19 | response | +| exception-xss.js:190:12:190:24 | req.params.id | +| exception-xss.js:190:12:190:24 | req.params.id | +| exception-xss.js:190:12:190:24 | req.params.id | | formatting.js:4:9:4:29 | evil | | formatting.js:4:16:4:29 | req.query.evil | | formatting.js:4:16:4:29 | req.query.evil | @@ -77,6 +80,7 @@ edges | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:9:16:9:53 | req.que ... e + ")" | | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:9:16:9:53 | req.que ... e + ")" | | etherpad.js:9:16:9:53 | req.que ... e + ")" | etherpad.js:9:5:9:53 | response | +| exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | | formatting.js:4:9:4:29 | evil | formatting.js:6:43:6:46 | evil | | formatting.js:4:9:4:29 | evil | formatting.js:7:49:7:52 | evil | | formatting.js:4:16:4:29 | req.query.evil | formatting.js:4:9:4:29 | evil | @@ -131,6 +135,7 @@ edges #select | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | ReflectedXss.js:8:33:8:45 | req.params.id | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value | | etherpad.js:11:12:11:19 | response | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:11:12:11:19 | response | Cross-site scripting vulnerability due to $@. | etherpad.js:9:16:9:30 | req.query.jsonp | user-provided value | +| exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | Cross-site scripting vulnerability due to $@. | exception-xss.js:190:12:190:24 | req.params.id | user-provided value | | formatting.js:6:14:6:47 | util.fo ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | | formatting.js:7:14:7:53 | require ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | | partial.js:10:14:10:18 | x + y | partial.js:13:42:13:48 | req.url | partial.js:10:14:10:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:13:42:13:48 | req.url | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected index 72c9ff0819f..560dca58553 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected @@ -1,4 +1,5 @@ | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value | +| exception-xss.js:190:12:190:24 | req.params.id | Cross-site scripting vulnerability due to $@. | exception-xss.js:190:12:190:24 | req.params.id | user-provided value | | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | | formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | | partial.js:10:14:10:18 | x + y | Cross-site scripting vulnerability due to $@. | partial.js:13:42:13:48 | req.url | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/exception-xss.js b/javascript/ql/test/query-tests/Security/CWE-079/exception-xss.js index a9cc01a0a76..005c5889edd 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/exception-xss.js +++ b/javascript/ql/test/query-tests/Security/CWE-079/exception-xss.js @@ -183,4 +183,34 @@ app.get('/user/:id', function (req, res) { } $('myId').html(res); // NOT OK! }); -}); \ No newline at end of file +}); + +app.get('/user/:id', function (req, res) { + try { + res.send(req.params.id); + } catch(err) { + res.send(err); // OK (the above `res.send()` is already reported by js/xss) + } +}); + +var fs = require("fs"); + +(function () { + var foo = document.location.search; + + try { + // A series of functions does not throw tainted exceptions. + Object.assign(foo, foo) + _.pick(foo, foo); + [foo, foo].join(join); + $.val(foo); + JSON.parse(foo); + /bla/.test(foo); + console.log(foo); + log.info(foo); + localStorage.setItem(foo); + } catch (e) { + $('myId').html(e); // OK + } + +})(); \ No newline at end of file From 9bea581a231330bf539555ee7432427a8919ec78 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 30 Jan 2020 14:29:56 +0100 Subject: [PATCH 099/148] Java: Improve taint for OutputStream.write and InputStream.read. --- .../dataflow/internal/TaintTrackingUtil.qll | 18 ++++++++----- .../test/library-tests/dataflow/taint/A.java | 27 +++++++++++++++++++ .../dataflow/taint/test.expected | 2 ++ .../test/library-tests/dataflow/taint/test.ql | 18 +++++++++++++ 4 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 java/ql/test/library-tests/dataflow/taint/A.java create mode 100644 java/ql/test/library-tests/dataflow/taint/test.expected create mode 100644 java/ql/test/library-tests/dataflow/taint/test.ql diff --git a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll index be33fc727c1..cb83df5b759 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/TaintTrackingUtil.qll @@ -256,9 +256,12 @@ private predicate taintPreservingQualifierToArgument(Method m, int arg) { m.hasName("writeTo") and arg = 0 or - m.getDeclaringType().hasQualifiedName("java.io", "InputStream") and - m.hasName("read") and - arg = 0 + exists(Method read | + m.overrides*(read) and + read.getDeclaringType().hasQualifiedName("java.io", "InputStream") and + read.hasName("read") and + arg = 0 + ) or m.getDeclaringType().getASupertype*().hasQualifiedName("java.io", "Reader") and m.hasName("read") and @@ -515,9 +518,12 @@ private predicate argToQualifierStep(Expr tracked, Expr sink) { * `arg` is the index of the argument. */ private predicate taintPreservingArgumentToQualifier(Method method, int arg) { - method.getDeclaringType().hasQualifiedName("java.io", "ByteArrayOutputStream") and - method.hasName("write") and - arg = 0 + exists(Method write | + method.overrides*(write) and + write.getDeclaringType().hasQualifiedName("java.io", "OutputStream") and + write.hasName("write") and + arg = 0 + ) } /** A comparison or equality test with a constant. */ diff --git a/java/ql/test/library-tests/dataflow/taint/A.java b/java/ql/test/library-tests/dataflow/taint/A.java new file mode 100644 index 00000000000..d6509f15936 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/taint/A.java @@ -0,0 +1,27 @@ +import java.io.*; + +public class A { + byte[] taint() { return new byte[2]; } + + void sink(Object o) { } + + void test1() { + ByteArrayOutputStream bOutput = new ByteArrayOutputStream(); + bOutput.write(taint(), 0, 1); + byte[] b = bOutput.toByteArray(); + ByteArrayInputStream bInput = new ByteArrayInputStream(b); + byte[] b2 = new byte[10]; + bInput.read(b2, 0, 1); + sink(b2); + } + + void test2() { + ByteArrayOutputStream bOutput = new ByteArrayOutputStream(); + bOutput.write(taint()); + byte[] b = bOutput.toByteArray(); + ByteArrayInputStream bInput = new ByteArrayInputStream(b); + byte[] b2 = new byte[10]; + bInput.read(b2); + sink(b2); + } +} diff --git a/java/ql/test/library-tests/dataflow/taint/test.expected b/java/ql/test/library-tests/dataflow/taint/test.expected new file mode 100644 index 00000000000..fdab0e9be24 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/taint/test.expected @@ -0,0 +1,2 @@ +| A.java:10:19:10:25 | taint(...) | A.java:15:10:15:11 | b2 | +| A.java:20:19:20:25 | taint(...) | A.java:25:10:25:11 | b2 | diff --git a/java/ql/test/library-tests/dataflow/taint/test.ql b/java/ql/test/library-tests/dataflow/taint/test.ql new file mode 100644 index 00000000000..65b15fbaa4e --- /dev/null +++ b/java/ql/test/library-tests/dataflow/taint/test.ql @@ -0,0 +1,18 @@ +import java +import semmle.code.java.dataflow.TaintTracking + +class Conf extends TaintTracking::Configuration { + Conf() { this = "qqconf" } + + override predicate isSource(DataFlow::Node n) { + n.asExpr().(MethodAccess).getMethod().hasName("taint") + } + + override predicate isSink(DataFlow::Node n) { + n.asExpr().(Argument).getCall().getCallee().hasName("sink") + } +} + +from DataFlow::Node src, DataFlow::Node sink, Conf conf +where conf.hasFlow(src, sink) +select src, sink From 8fc273b9eca688065d7e823aaad0f7649df34037 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 29 Jan 2020 14:00:26 +0100 Subject: [PATCH 100/148] update expected output --- javascript/extractor/tests/shebang/output/trap/tst.html.trap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/extractor/tests/shebang/output/trap/tst.html.trap b/javascript/extractor/tests/shebang/output/trap/tst.html.trap index 3f56959df24..fc0a476640a 100644 --- a/javascript/extractor/tests/shebang/output/trap/tst.html.trap +++ b/javascript/extractor/tests/shebang/output/trap/tst.html.trap @@ -14,7 +14,7 @@ toplevels(#20001,1) locations_default(#20002,#10000,3,17,3,17) hasLocation(#20001,#20002) #20003=* -jsParseErrors(#20003,#20001,"Error: Unexpected character '#' (U+0023)","#!/usr/bin/node +jsParseErrors(#20003,#20001,"Error: Unexpected token","#!/usr/bin/node ") #20004=@"loc,{#10000},4,1,4,1" locations_default(#20004,#10000,4,1,4,1) From 2a0a568cbbec90b8dde3646c884ecb2341d1e18c Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 30 Jan 2020 17:04:35 +0100 Subject: [PATCH 101/148] Java: Remove duplicate class. --- java/ql/src/semmle/code/java/security/Random.qll | 4 ---- 1 file changed, 4 deletions(-) diff --git a/java/ql/src/semmle/code/java/security/Random.qll b/java/ql/src/semmle/code/java/security/Random.qll index 7929309821c..095b619c9c6 100644 --- a/java/ql/src/semmle/code/java/security/Random.qll +++ b/java/ql/src/semmle/code/java/security/Random.qll @@ -36,10 +36,6 @@ private class PredictableSeedFlowConfiguration extends DataFlow::Configuration { } } -private class TypeNumber extends Class { - TypeNumber() { this.getQualifiedName() = "java.lang.Number" } -} - private predicate predictableCalcStep(Expr e1, Expr e2) { e2.(BinaryExpr).hasOperands(e1, any(PredictableSeedExpr p)) or From db55ec250ad14403ad0a286dbe1e49464dc81ded Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Thu, 30 Jan 2020 22:32:36 +0100 Subject: [PATCH 102/148] Rename CWE-90 to CWE-090 --- java/ql/src/Security/CWE/{CWE-90 => CWE-090}/LdapInjection.qhelp | 0 java/ql/src/Security/CWE/{CWE-90 => CWE-090}/LdapInjection.ql | 0 .../src/Security/CWE/{CWE-90 => CWE-090}/LdapInjectionApache.java | 0 .../src/Security/CWE/{CWE-90 => CWE-090}/LdapInjectionJndi.java | 0 java/ql/src/Security/CWE/{CWE-90 => CWE-090}/LdapInjectionLib.qll | 0 .../src/Security/CWE/{CWE-90 => CWE-090}/LdapInjectionSpring.java | 0 .../Security/CWE/{CWE-90 => CWE-090}/LdapInjectionUnboundId.java | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename java/ql/src/Security/CWE/{CWE-90 => CWE-090}/LdapInjection.qhelp (100%) rename java/ql/src/Security/CWE/{CWE-90 => CWE-090}/LdapInjection.ql (100%) rename java/ql/src/Security/CWE/{CWE-90 => CWE-090}/LdapInjectionApache.java (100%) rename java/ql/src/Security/CWE/{CWE-90 => CWE-090}/LdapInjectionJndi.java (100%) rename java/ql/src/Security/CWE/{CWE-90 => CWE-090}/LdapInjectionLib.qll (100%) rename java/ql/src/Security/CWE/{CWE-90 => CWE-090}/LdapInjectionSpring.java (100%) rename java/ql/src/Security/CWE/{CWE-90 => CWE-090}/LdapInjectionUnboundId.java (100%) diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp b/java/ql/src/Security/CWE/CWE-090/LdapInjection.qhelp similarity index 100% rename from java/ql/src/Security/CWE/CWE-90/LdapInjection.qhelp rename to java/ql/src/Security/CWE/CWE-090/LdapInjection.qhelp diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjection.ql b/java/ql/src/Security/CWE/CWE-090/LdapInjection.ql similarity index 100% rename from java/ql/src/Security/CWE/CWE-90/LdapInjection.ql rename to java/ql/src/Security/CWE/CWE-090/LdapInjection.ql diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionApache.java b/java/ql/src/Security/CWE/CWE-090/LdapInjectionApache.java similarity index 100% rename from java/ql/src/Security/CWE/CWE-90/LdapInjectionApache.java rename to java/ql/src/Security/CWE/CWE-090/LdapInjectionApache.java diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionJndi.java b/java/ql/src/Security/CWE/CWE-090/LdapInjectionJndi.java similarity index 100% rename from java/ql/src/Security/CWE/CWE-90/LdapInjectionJndi.java rename to java/ql/src/Security/CWE/CWE-090/LdapInjectionJndi.java diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll b/java/ql/src/Security/CWE/CWE-090/LdapInjectionLib.qll similarity index 100% rename from java/ql/src/Security/CWE/CWE-90/LdapInjectionLib.qll rename to java/ql/src/Security/CWE/CWE-090/LdapInjectionLib.qll diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionSpring.java b/java/ql/src/Security/CWE/CWE-090/LdapInjectionSpring.java similarity index 100% rename from java/ql/src/Security/CWE/CWE-90/LdapInjectionSpring.java rename to java/ql/src/Security/CWE/CWE-090/LdapInjectionSpring.java diff --git a/java/ql/src/Security/CWE/CWE-90/LdapInjectionUnboundId.java b/java/ql/src/Security/CWE/CWE-090/LdapInjectionUnboundId.java similarity index 100% rename from java/ql/src/Security/CWE/CWE-90/LdapInjectionUnboundId.java rename to java/ql/src/Security/CWE/CWE-090/LdapInjectionUnboundId.java From 3fd8d9eb5c60721d62348bdec88f65086a37bf18 Mon Sep 17 00:00:00 2001 From: Grzegorz Golawski Date: Thu, 30 Jan 2020 22:33:20 +0100 Subject: [PATCH 103/148] Rename CWE-90 into CWE-090 --- java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref b/java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref index 2386248dc51..1af3794de3a 100644 --- a/java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref +++ b/java/ql/test/query-tests/security/CWE-090/LdapInjection.qlref @@ -1 +1 @@ -Security/CWE/CWE-90/LdapInjection.ql +Security/CWE/CWE-090/LdapInjection.ql From 83d611de11ff7543507193d1022d764e95ad313b Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Thu, 30 Jan 2020 15:19:59 -0800 Subject: [PATCH 104/148] C++: don't conflate pointers in data flow --- .../cpp/ir/dataflow/internal/DataFlowUtil.qll | 57 +++++++++++++------ .../defaulttainttracking.cpp | 9 +++ .../DefaultTaintTracking/tainted.expected | 8 +++ .../DefaultTaintTracking/test_diff.expected | 6 ++ .../dataflow/taint-tests/test_diff.expected | 3 + .../dataflow/taint-tests/test_ir.expected | 3 - 6 files changed, 67 insertions(+), 19 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll index 01fee2728a7..5cca7894997 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll @@ -291,25 +291,50 @@ private predicate simpleInstructionLocalFlowStep(Instruction iFrom, Instruction // for now. iTo.getAnOperand().(ChiTotalOperand).getDef() = iFrom or - // Flow from argument to return value - iTo = any(CallInstruction call | - exists(int indexIn | - modelFlowToReturnValue(call.getStaticCallTarget(), indexIn) and - iFrom = getACallArgumentOrIndirection(call, indexIn) - ) - ) - or - // Flow from input argument to output argument - // TODO: This won't work in practice as long as all aliased memory is tracked - // together in a single virtual variable. - iTo = any(WriteSideEffectInstruction outNode | - exists(CallInstruction call, int indexIn, int indexOut | - modelFlowToParameter(call.getStaticCallTarget(), indexIn, indexOut) and - iFrom = getACallArgumentOrIndirection(call, indexIn) and - outNode.getIndex() = indexOut and + // Flow through modeled functions + modelFlow(iFrom, iTo) +} + +private predicate modelFlow(Instruction iFrom, Instruction iTo) { + exists( + CallInstruction call, DataFlowFunction func, FunctionInput modelIn, FunctionOutput modelOut + | + call.getStaticCallTarget() = func and + func.hasDataFlow(modelIn, modelOut) + | + ( + modelOut.isReturnValue() and + iTo = call + or + // TODO: Add write side effects for return values + modelOut.isReturnValueDeref() and + iTo = call + or + exists(WriteSideEffectInstruction outNode | + modelOut.isParameterDeref(outNode.getIndex()) and + iTo = outNode and outNode.getPrimaryInstruction() = call ) + // TODO: add write side effects for qualifiers + ) and + ( + exists(int index | + modelIn.isParameter(index) and + iFrom = call.getPositionalArgument(index) + ) + or + exists(int index, ReadSideEffectInstruction read | + modelIn.isParameterDeref(index) and + read.getIndex() = index and + read.getPrimaryInstruction() = call and + iFrom = read.getSideEffectOperand().getAnyDef() + ) + or + modelIn.isQualifierAddress() and + iFrom = call.getThisArgument() + // TODO: add read side effects for qualifiers ) + ) } /** diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/defaulttainttracking.cpp b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/defaulttainttracking.cpp index ebe38f1f060..02ae1e07154 100644 --- a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/defaulttainttracking.cpp +++ b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/defaulttainttracking.cpp @@ -77,4 +77,13 @@ void test_dynamic_cast() { reinterpret_cast(b2)->f(getenv("VAR")); dynamic_cast(b2)->f(getenv("VAR")); // tainted [FALSE POSITIVE] +} + +namespace std { + template< class T > + T&& move( T&& t ) noexcept; +} + +void test_std_move() { + sink(std::move(getenv("VAR"))); } \ No newline at end of file diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected index 92948eeffc6..ddb07575064 100644 --- a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected +++ b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected @@ -89,6 +89,14 @@ | defaulttainttracking.cpp:79:30:79:35 | call to getenv | defaulttainttracking.cpp:79:30:79:35 | call to getenv | | defaulttainttracking.cpp:79:30:79:35 | call to getenv | defaulttainttracking.cpp:79:30:79:42 | (const char *)... | | defaulttainttracking.cpp:79:30:79:35 | call to getenv | test_diff.cpp:1:11:1:20 | p#0 | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:9:11:9:20 | p#0 | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:84:17:84:17 | t | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:16 | call to move | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:32 | (const char *)... | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:32 | (reference dereference) | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:18:88:23 | call to getenv | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:18:88:30 | (reference to) | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | test_diff.cpp:1:11:1:20 | p#0 | | test_diff.cpp:92:10:92:13 | argv | defaulttainttracking.cpp:9:11:9:20 | p#0 | | test_diff.cpp:92:10:92:13 | argv | test_diff.cpp:1:11:1:20 | p#0 | | test_diff.cpp:92:10:92:13 | argv | test_diff.cpp:92:10:92:13 | argv | diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected index c557f43f1c7..27aad1725a8 100644 --- a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected @@ -9,6 +9,12 @@ | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:36:39:61 | (const char *)... | AST only | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:51:39:61 | env_pointer | AST only | | defaulttainttracking.cpp:64:10:64:15 | call to getenv | defaulttainttracking.cpp:52:24:52:24 | p | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:9:11:9:20 | p#0 | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:16 | call to move | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:32 | (const char *)... | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:8:88:32 | (reference dereference) | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | defaulttainttracking.cpp:88:18:88:30 | (reference to) | IR only | +| defaulttainttracking.cpp:88:18:88:23 | call to getenv | test_diff.cpp:1:11:1:20 | p#0 | IR only | | test_diff.cpp:104:12:104:15 | argv | test_diff.cpp:104:11:104:20 | (...) | IR only | | test_diff.cpp:108:10:108:13 | argv | test_diff.cpp:36:24:36:24 | p | AST only | | test_diff.cpp:111:10:111:13 | argv | defaulttainttracking.cpp:9:11:9:20 | p#0 | AST only | diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected index f41cc776510..7680193da16 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/test_diff.expected @@ -32,6 +32,9 @@ | taint.cpp:261:7:261:7 | taint.cpp:258:7:258:12 | AST only | | taint.cpp:351:7:351:7 | taint.cpp:330:6:330:11 | AST only | | taint.cpp:352:7:352:7 | taint.cpp:330:6:330:11 | AST only | +| taint.cpp:372:7:372:7 | taint.cpp:365:24:365:29 | AST only | +| taint.cpp:374:7:374:7 | taint.cpp:365:24:365:29 | AST only | +| taint.cpp:391:7:391:7 | taint.cpp:385:27:385:32 | AST only | | taint.cpp:423:7:423:7 | taint.cpp:422:14:422:19 | AST only | | taint.cpp:424:9:424:17 | taint.cpp:422:14:422:19 | AST only | | taint.cpp:429:7:429:7 | taint.cpp:428:13:428:18 | IR only | diff --git a/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected b/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected index 49ff8144a6e..b38de345220 100644 --- a/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected +++ b/cpp/ql/test/library-tests/dataflow/taint-tests/test_ir.expected @@ -17,9 +17,6 @@ | taint.cpp:291:7:291:7 | y | taint.cpp:275:6:275:11 | call to source | | taint.cpp:337:7:337:7 | t | taint.cpp:330:6:330:11 | call to source | | taint.cpp:350:7:350:7 | t | taint.cpp:330:6:330:11 | call to source | -| taint.cpp:372:7:372:7 | a | taint.cpp:365:24:365:29 | source | -| taint.cpp:374:7:374:7 | c | taint.cpp:365:24:365:29 | source | | taint.cpp:382:7:382:7 | a | taint.cpp:377:23:377:28 | source | -| taint.cpp:391:7:391:7 | a | taint.cpp:385:27:385:32 | source | | taint.cpp:429:7:429:7 | b | taint.cpp:428:13:428:18 | call to source | | taint.cpp:430:9:430:14 | member | taint.cpp:428:13:428:18 | call to source | From 279c584bb85f14a410abb1e7d54e4e0649fef2bd Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 31 Jan 2020 10:55:29 +0100 Subject: [PATCH 105/148] fix FP in js/path-injection by recognizing more prefix checks --- .../Security/CWE-400/PrototypePollutionUtility.ql | 14 +------------- .../ql/src/semmle/javascript/GlobalAccessPaths.qll | 12 ++++++++++++ javascript/ql/src/semmle/javascript/StringOps.qll | 4 ++-- .../StringOps/StartsWith/StartsWith.expected | 2 ++ .../test/library-tests/StringOps/StartsWith/tst.js | 2 ++ 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index 9e1e38662fc..608f0d407a1 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -47,25 +47,13 @@ abstract class EnumeratedPropName extends DataFlow::Node { */ abstract DataFlow::Node getSourceObject(); - /** - * Gets a local reference of the source object. - */ - SourceNode getASourceObjectRef() { - exists(SourceNode root, string path | - getSourceObject() = AccessPath::getAReferenceTo(root, path) and - result = AccessPath::getAReferenceTo(root, path) - ) - or - result = getSourceObject().getALocalSource() - } - /** * Gets a property read that accesses the corresponding property value in the source object. * * For example, gets `src[key]` in `for (var key in src) { src[key]; }`. */ PropRead getASourceProp() { - result = getASourceObjectRef().getAPropertyRead() and + result = AccessPath::getASourceAccess(getSourceObject()).getAPropertyRead() and result.getPropertyNameExpr().flow().getImmediatePredecessor*() = this } } diff --git a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll index 35755fb6c8b..1e7452f878b 100644 --- a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll +++ b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll @@ -412,4 +412,16 @@ module AccessPath { isAssignedInUniqueFile(name) ) } + + /** + * Gets a SourceNode that is accessed using the same access path as the input. + */ + DataFlow::SourceNode getASourceAccess(DataFlow::Node node) { + exists(DataFlow::SourceNode root, string accessPath | + node = AccessPath::getAReferenceTo(root, accessPath) and + result = AccessPath::getAReferenceTo(root, accessPath) + ) + or + result = node.getALocalSource() + } } diff --git a/javascript/ql/src/semmle/javascript/StringOps.qll b/javascript/ql/src/semmle/javascript/StringOps.qll index d916e8e469d..06791ec19d4 100644 --- a/javascript/ql/src/semmle/javascript/StringOps.qll +++ b/javascript/ql/src/semmle/javascript/StringOps.qll @@ -165,10 +165,10 @@ module StringOps { StartsWith_Substring() { astNode.hasOperands(call.asExpr(), substring.asExpr()) and - (call.getMethodName() = "substring" or call.getMethodName() = "substr") and + (call.getMethodName() = "substring" or call.getMethodName() = "substr" or call.getMethodName() = "slice") and call.getNumArgument() = 2 and ( - substring.getALocalSource().getAPropertyRead("length").flowsTo(call.getArgument(1)) + AccessPath::getASourceAccess(substring).getAPropertyRead("length").flowsTo(call.getArgument(1)) or substring.getStringValue().length() = call.getArgument(1).asExpr().getIntValue() ) diff --git a/javascript/ql/test/library-tests/StringOps/StartsWith/StartsWith.expected b/javascript/ql/test/library-tests/StringOps/StartsWith/StartsWith.expected index 3910799bd75..d072e023cd2 100644 --- a/javascript/ql/test/library-tests/StringOps/StartsWith/StartsWith.expected +++ b/javascript/ql/test/library-tests/StringOps/StartsWith/StartsWith.expected @@ -14,3 +14,5 @@ | tst.js:19:9:19:36 | A.subst ... "web/" | tst.js:19:9:19:9 | A | tst.js:19:31:19:36 | "web/" | true | | tst.js:32:9:32:32 | strings ... h(A, B) | tst.js:32:28:32:28 | A | tst.js:32:31:32:31 | B | true | | tst.js:33:9:33:47 | strings ... h(A, B) | tst.js:33:43:33:43 | A | tst.js:33:46:33:46 | B | true | +| tst.js:34:9:34:34 | A.slice ... ) !== B | tst.js:34:9:34:9 | A | tst.js:34:34:34:34 | B | false | +| tst.js:35:9:35:42 | A.slice ... = B.foo | tst.js:35:9:35:9 | A | tst.js:35:38:35:42 | B.foo | false | diff --git a/javascript/ql/test/library-tests/StringOps/StartsWith/tst.js b/javascript/ql/test/library-tests/StringOps/StartsWith/tst.js index 97bbf240ec6..0616718c9f3 100644 --- a/javascript/ql/test/library-tests/StringOps/StartsWith/tst.js +++ b/javascript/ql/test/library-tests/StringOps/StartsWith/tst.js @@ -31,4 +31,6 @@ function f(A, B) { if (strings.startsWith(A, B)) {} if (strings.caseInsensitiveStartsWith(A, B)) {} + if (A.slice(0, B.length) !== B) {} + if (A.slice(0, B.foo.length) !== B.foo) {} } From 18a8c2b2206fb9442fbe7df31ad5e3756ef46181 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 31 Jan 2020 11:39:46 +0100 Subject: [PATCH 106/148] Java: Add qlpack.yml in upgrades. --- java/upgrades/qlpack.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 java/upgrades/qlpack.yml diff --git a/java/upgrades/qlpack.yml b/java/upgrades/qlpack.yml new file mode 100644 index 00000000000..aee4ed51c3e --- /dev/null +++ b/java/upgrades/qlpack.yml @@ -0,0 +1,2 @@ +name: codeql-java-upgrades +upgrades: . From b6611b1fb3acaf75ad9f1b8f77eeb2a645d573de Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 31 Jan 2020 12:24:12 +0100 Subject: [PATCH 107/148] add "slice" as a recognized prefix method in ClientSideUrlRedirectCustomizations.qll --- .../security/dataflow/ClientSideUrlRedirectCustomizations.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll index 02326bcdb92..f8d5c798e1e 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/ClientSideUrlRedirectCustomizations.qll @@ -55,7 +55,7 @@ module ClientSideUrlRedirect { // exclude `location.href.split('?')[0]`, which can never refer to the query string not exists(PropAccess pacc | mce = pacc.getBase() | pacc.getPropertyName() = "0") or - (methodName = "substring" or methodName = "substr") and + (methodName = "substring" or methodName = "substr" or methodName = "slice") and // exclude `location.href.substring(0, ...)` and similar, which can // never refer to the query string not mce.getArgument(0).(NumberLiteral).getIntValue() = 0 From ba2bbf1788828b82748fb69ffc3438d96d1a1a15 Mon Sep 17 00:00:00 2001 From: Taus Brock-Nannestad Date: Fri, 31 Jan 2020 12:33:02 +0100 Subject: [PATCH 108/148] Python: Extend `Value` API. Adds - `StringValue` as a new class, - `Value::booleanValue` which returns the boolean interpretation of the given value, and - `ClassValue::str` which returns the value of the `str` class, depending on the Python version. --- .../src/semmle/python/objects/ObjectAPI.qll | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 8cf38d62697..f84f7c2bc4c 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -117,6 +117,11 @@ class Value extends TObject { my_class.getABaseType+() = other_class ) } + + /** Gets the boolean value of this value. */ + boolean booleanValue() { + result = this.(ObjectInternal).booleanValue() + } } /** Class representing modules in the Python program @@ -594,6 +599,22 @@ class TupleValue extends SequenceValue { } +/** A class representing strings, either present in the source as a literal, or +in a builtin as a value. */ + +class StringValue extends Value { + StringValue() { + this instanceof BytesObjectInternal or + this instanceof UnicodeObjectInternal + } + + string getText() { + result = this.(BytesObjectInternal).strValue() + or + result = this.(UnicodeObjectInternal).strValue() + } +} + /** A method-resolution-order sequence of classes */ class MRO extends TClassList { @@ -684,6 +705,15 @@ module ClassValue { result = TBuiltinClassObject(Builtin::special("unicode")) } + /** Get the `ClassValue` for the `str` class. This is `bytes` in Python 2, + and `str` in Python 3. */ + ClassValue str() { + if major_version() = 2 then + result = bytes() + else + result = unicode() + } + /** Get the `ClassValue` for the `classmethod` class. */ ClassValue classmethod() { result = TBuiltinClassObject(Builtin::special("ClassMethod")) From e6d46b92791ad7855c85d198771102c8497b6177 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 31 Jan 2020 12:35:03 +0100 Subject: [PATCH 109/148] add test for new prefix check on TaintedPath --- .../CWE-022/TaintedPath/TaintedPath.expected | 67 +++++++++++++++++++ .../CWE-022/TaintedPath/normalizedPaths.js | 18 +++++ 2 files changed, 85 insertions(+) diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected index 056f020660c..9f981456940 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected @@ -1145,6 +1145,34 @@ nodes | normalizedPaths.js:228:21:228:24 | path | | normalizedPaths.js:228:21:228:24 | path | | normalizedPaths.js:228:21:228:24 | path | +| normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | +| normalizedPaths.js:236:33:236:46 | req.query.path | +| normalizedPaths.js:236:33:236:46 | req.query.path | +| normalizedPaths.js:236:33:236:46 | req.query.path | +| normalizedPaths.js:236:33:236:46 | req.query.path | +| normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:250:21:250:24 | path | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | @@ -2903,6 +2931,42 @@ edges | normalizedPaths.js:226:35:226:48 | req.query.path | normalizedPaths.js:226:14:226:49 | pathMod ... y.path) | | normalizedPaths.js:226:35:226:48 | req.query.path | normalizedPaths.js:226:14:226:49 | pathMod ... y.path) | | normalizedPaths.js:226:35:226:48 | req.query.path | normalizedPaths.js:226:14:226:49 | pathMod ... y.path) | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:238:19:238:22 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:245:21:245:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:7:236:47 | path | normalizedPaths.js:250:21:250:24 | path | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | normalizedPaths.js:236:7:236:47 | path | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | +| normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:236:14:236:47 | pathMod ... y.path) | | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | @@ -3016,6 +3080,9 @@ edges | normalizedPaths.js:210:21:210:34 | normalizedPath | normalizedPaths.js:174:14:174:27 | req.query.path | normalizedPaths.js:210:21:210:34 | normalizedPath | This path depends on $@. | normalizedPaths.js:174:14:174:27 | req.query.path | a user-provided value | | normalizedPaths.js:222:21:222:24 | path | normalizedPaths.js:214:35:214:48 | req.query.path | normalizedPaths.js:222:21:222:24 | path | This path depends on $@. | normalizedPaths.js:214:35:214:48 | req.query.path | a user-provided value | | normalizedPaths.js:228:21:228:24 | path | normalizedPaths.js:226:35:226:48 | req.query.path | normalizedPaths.js:228:21:228:24 | path | This path depends on $@. | normalizedPaths.js:226:35:226:48 | req.query.path | a user-provided value | +| normalizedPaths.js:238:19:238:22 | path | normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:238:19:238:22 | path | This path depends on $@. | normalizedPaths.js:236:33:236:46 | req.query.path | a user-provided value | +| normalizedPaths.js:245:21:245:24 | path | normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:245:21:245:24 | path | This path depends on $@. | normalizedPaths.js:236:33:236:46 | req.query.path | a user-provided value | +| normalizedPaths.js:250:21:250:24 | path | normalizedPaths.js:236:33:236:46 | req.query.path | normalizedPaths.js:250:21:250:24 | path | This path depends on $@. | normalizedPaths.js:236:33:236:46 | req.query.path | a user-provided value | | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | This path depends on $@. | tainted-require.js:7:19:7:37 | req.param("module") | a user-provided value | | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | a user-provided value | | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js index cc55a3422dc..c0be777dd84 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js @@ -231,3 +231,21 @@ app.get('/replace', (req, res) => { fs.readFileSync(path); // OK } }); + +app.get('/resolve-path', (req, res) => { + let path = pathModule.resolve(req.query.path); + + fs.readFileSync(path); // NOT OK + + var self = something(); + + if (path.substring(0, self.dir.length) === self.dir) + fs.readFileSync(path); // OK + else + fs.readFileSync(path); // NOT OK - wrong polarity + + if (path.slice(0, self.dir.length) === self.dir) + fs.readFileSync(path); // OK + else + fs.readFileSync(path); // NOT OK - wrong polarity +}); From cd688367c7d5236229224f2faae3c6aa2006c6be Mon Sep 17 00:00:00 2001 From: alexet Date: Thu, 30 Jan 2020 18:49:42 +0000 Subject: [PATCH 110/148] CPP: Avoid uncessary recursion --- cpp/ql/src/semmle/code/cpp/commons/Printf.qll | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/commons/Printf.qll b/cpp/ql/src/semmle/code/cpp/commons/Printf.qll index 8c041351fd0..32cea249214 100644 --- a/cpp/ql/src/semmle/code/cpp/commons/Printf.qll +++ b/cpp/ql/src/semmle/code/cpp/commons/Printf.qll @@ -258,14 +258,7 @@ class FormatLiteral extends Literal { * Gets the position in the string at which the nth conversion specifier * starts. */ - int getConvSpecOffset(int n) { - n = 0 and result = this.getFormat().indexOf("%", 0, 0) - or - n > 0 and - exists(int p | - n = p + 1 and result = this.getFormat().indexOf("%", 0, this.getConvSpecOffset(p) + 2) - ) - } + int getConvSpecOffset(int n) { result = this.getFormat().indexOf("%", n, 0) } /* * Each of these predicates gets a regular expressions to match each individual From 72114a48f5239ebf4fe93f28b597164bda000d1b Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 31 Jan 2020 15:34:58 +0100 Subject: [PATCH 111/148] rename getASourceAccess to getAnAliasedSourceNode --- javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql | 2 +- javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll | 2 +- javascript/ql/src/semmle/javascript/StringOps.qll | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index 608f0d407a1..2d66dd03a99 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -53,7 +53,7 @@ abstract class EnumeratedPropName extends DataFlow::Node { * For example, gets `src[key]` in `for (var key in src) { src[key]; }`. */ PropRead getASourceProp() { - result = AccessPath::getASourceAccess(getSourceObject()).getAPropertyRead() and + result = AccessPath::getAnAliasedSourceNode(getSourceObject()).getAPropertyRead() and result.getPropertyNameExpr().flow().getImmediatePredecessor*() = this } } diff --git a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll index 1e7452f878b..63add0a9864 100644 --- a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll +++ b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll @@ -416,7 +416,7 @@ module AccessPath { /** * Gets a SourceNode that is accessed using the same access path as the input. */ - DataFlow::SourceNode getASourceAccess(DataFlow::Node node) { + DataFlow::SourceNode getAnAliasedSourceNode(DataFlow::Node node) { exists(DataFlow::SourceNode root, string accessPath | node = AccessPath::getAReferenceTo(root, accessPath) and result = AccessPath::getAReferenceTo(root, accessPath) diff --git a/javascript/ql/src/semmle/javascript/StringOps.qll b/javascript/ql/src/semmle/javascript/StringOps.qll index 06791ec19d4..fac784cb49f 100644 --- a/javascript/ql/src/semmle/javascript/StringOps.qll +++ b/javascript/ql/src/semmle/javascript/StringOps.qll @@ -168,7 +168,7 @@ module StringOps { (call.getMethodName() = "substring" or call.getMethodName() = "substr" or call.getMethodName() = "slice") and call.getNumArgument() = 2 and ( - AccessPath::getASourceAccess(substring).getAPropertyRead("length").flowsTo(call.getArgument(1)) + AccessPath::getAnAliasedSourceNode(substring).getAPropertyRead("length").flowsTo(call.getArgument(1)) or substring.getStringValue().length() = call.getArgument(1).asExpr().getIntValue() ) From 32bcb18cdfd38a7bbcdd74c223168e7e45559a61 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 31 Jan 2020 15:35:38 +0100 Subject: [PATCH 112/148] add pragma[inline] to getAnAliasedSourceNode --- javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll | 1 + 1 file changed, 1 insertion(+) diff --git a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll index 63add0a9864..50d4848bc86 100644 --- a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll +++ b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll @@ -416,6 +416,7 @@ module AccessPath { /** * Gets a SourceNode that is accessed using the same access path as the input. */ + pragma[inline] DataFlow::SourceNode getAnAliasedSourceNode(DataFlow::Node node) { exists(DataFlow::SourceNode root, string accessPath | node = AccessPath::getAReferenceTo(root, accessPath) and From 84be6e1286e3fb0a8dc24cbd5c1a9f15fd1f281e Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 31 Jan 2020 15:38:19 +0100 Subject: [PATCH 113/148] update docString on getAnAliasedSourceNode --- javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll index 50d4848bc86..2a3d8f963a2 100644 --- a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll +++ b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll @@ -414,7 +414,7 @@ module AccessPath { } /** - * Gets a SourceNode that is accessed using the same access path as the input. + * Gets a `SourceNode` that refers to the same value or access path as the given node. */ pragma[inline] DataFlow::SourceNode getAnAliasedSourceNode(DataFlow::Node node) { From a1aed1ad933df8dcf18dc91776614849349acf0f Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Fri, 31 Jan 2020 13:07:39 +0100 Subject: [PATCH 114/148] C++: Workaround for problem with memcpy flow The type of the source argument to `memcpy` is `void *`, and somehow that meant that the copied object itself got type `void`. Since that has size 0, the SSA construction did not model it as reading from the last write. This is probably not the right fix, but maybe it's good enough for now. The right fix would ensure that the type reported by `hasOperandMemoryAccess` is `UnknownType`. When `DefaultTaintTracking.qll` is enabled, this commit has the effect of restoring a lost results: --- a/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/OverflowDestination.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/OverflowDestination.expected @@ -1 +1,2 @@ | overflowdestination.cpp:30:2:30:8 | call to strncpy | To avoid overflow, this operation should be bounded by destination-buffer size, not source-buffer size. | +| overflowdestination.cpp:46:2:46:7 | call to memcpy | To avoid overflow, this operation should be bounded by destination-buffer size, not source-buffer size. | --- .../cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll index bcf3e5db19e..f372988ea3d 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll @@ -26,7 +26,7 @@ private predicate hasResultMemoryAccess( type = languageType.getIRType() and isIndirectOrBufferMemoryAccess(instr.getResultMemoryAccess()) and (if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and - if exists(type.getByteSize()) + if type.getByteSize() > 0 then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) else endBitOffset = Ints::unknown() ) @@ -43,7 +43,7 @@ private predicate hasOperandMemoryAccess( type = languageType.getIRType() and isIndirectOrBufferMemoryAccess(operand.getMemoryAccess()) and (if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and - if exists(type.getByteSize()) + if type.getByteSize() > 0 then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) else endBitOffset = Ints::unknown() ) From 83f807f1826b369ffabf37a03206c5d12dcd6e79 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Fri, 31 Jan 2020 13:31:34 +0100 Subject: [PATCH 115/148] C++: Interprocedural indirection taint tracking As a temporary workaround in the `DefaultTaintTracking` library, we funnel flow across calls by conflating pointer and object both at the caller and the callee. The three cases in `adjustedSink` were deleted because they are now covered by the one case for `ReadSideEffectInstruction` in `instructionTaintStep`. When enabling `DefaultTaintTracking`, this commit on top of #2736 has the effect effect of recovering two lost results: --- a/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/OverflowDestination.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/OverflowDestination.expected @@ -1,2 +1,4 @@ | overflowdestination.cpp:30:2:30:8 | call to strncpy | To avoid overflow, this operation should be bounded by destination-buffer size, not source-buffer size. | | overflowdestination.cpp:46:2:46:7 | call to memcpy | To avoid overflow, this operation should be bounded by destination-buffer size, not source-buffer size. | +| overflowdestination.cpp:53:2:53:7 | call to memcpy | To avoid overflow, this operation should be bounded by destination-buffer size, not source-buffer size. | +| overflowdestination.cpp:64:2:64:7 | call to memcpy | To avoid overflow, this operation should be bounded by destination-buffer size, not source-buffer size. | In the internal repo, we recover one lost result. Additionally, there are two queries that gain an extra source for an existing sink. I'll classify that as noise. The new results look like this: foo(argv); // this `argv` is a new source for the sink in `bar` bar(argv); // this `argv` is the existing source for the sink in `bar` --- .../cpp/ir/dataflow/DefaultTaintTracking.qll | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll index e999b893a14..a006246fc33 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -151,6 +151,22 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) { // from `a`. i2.(PointerAddInstruction).getLeft() = i1 or + // Until we have from through indirections across calls, we'll take flow out + // of the parameter and into its indirection. + exists(IRFunction f, Parameter parameter | + initializeParameter(f, parameter, i1) and + initializeIndirection(f, parameter, i2) + ) + or + // Until we have flow through indirections across calls, we'll take flow out + // of the indirection and into the argument. + // When we get proper flow through indirections across calls, this code can be + // moved to `adjusedSink` or possibly into the `DataFlow::ExprNode` class. + exists(ReadSideEffectInstruction read | + read.getAnOperand().(SideEffectOperand).getAnyDef() = i1 and + read.getArgumentDef() = i2 + ) + or // Flow from argument to return value i2 = any(CallInstruction call | @@ -176,6 +192,18 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) { ) } +pragma[noinline] +private predicate initializeIndirection(IRFunction f, Parameter p, InitializeIndirectionInstruction instr) { + instr.getParameter() = p and + instr.getEnclosingIRFunction() = f +} + +pragma[noinline] +private predicate initializeParameter(IRFunction f, Parameter p, InitializeParameterInstruction instr) { + instr.getParameter() = p and + instr.getEnclosingIRFunction() = f +} + /** * Get an instruction that goes into argument `argumentIndex` of `call`. This * can be either directly or through one pointer indirection. @@ -273,23 +301,6 @@ private Element adjustedSink(DataFlow::Node sink) { // For compatibility, send flow into a `NotExpr` even if it's part of a // short-circuiting condition and thus might get skipped. result.(NotExpr).getOperand() = sink.asExpr() - or - // For compatibility, send flow from argument read side effects to their - // corresponding argument expression - exists(IndirectReadSideEffectInstruction read | - read.getAnOperand().(SideEffectOperand).getAnyDef() = sink.asInstruction() and - read.getArgumentDef().getUnconvertedResultExpression() = result - ) - or - exists(BufferReadSideEffectInstruction read | - read.getAnOperand().(SideEffectOperand).getAnyDef() = sink.asInstruction() and - read.getArgumentDef().getUnconvertedResultExpression() = result - ) - or - exists(SizedBufferReadSideEffectInstruction read | - read.getAnOperand().(SideEffectOperand).getAnyDef() = sink.asInstruction() and - read.getArgumentDef().getUnconvertedResultExpression() = result - ) } predicate tainted(Expr source, Element tainted) { From 7647d940685d59284af39541ce109ab265265709 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 31 Jan 2020 16:48:35 +0100 Subject: [PATCH 116/148] Java: Add change note for LDAP injection query. --- change-notes/1.24/analysis-java.md | 1 + 1 file changed, 1 insertion(+) diff --git a/change-notes/1.24/analysis-java.md b/change-notes/1.24/analysis-java.md index 0dc17be979f..0c742201970 100644 --- a/change-notes/1.24/analysis-java.md +++ b/change-notes/1.24/analysis-java.md @@ -12,6 +12,7 @@ The following changes in version 1.24 affect Java analysis in all applications. |-----------------------------|-----------|--------------------------------------------------------------------| | Disabled Spring CSRF protection (`java/spring-disabled-csrf-protection`) | security, external/cwe/cwe-352 | Finds disabled Cross-Site Request Forgery (CSRF) protection in Spring. | | Failure to use HTTPS or SFTP URL in Maven artifact upload/download (`java/maven/non-https-url`) | security, external/cwe/cwe-300, external/cwe/cwe-319, external/cwe/cwe-494, external/cwe/cwe-829 | Finds use of insecure protocols during Maven dependency resolution. Results are shown on LGTM by default. | +| LDAP query built from user-controlled sources (`java/ldap-injection`) | security, external/cwe/cwe-090 | Finds LDAP queries vulnerable to injection of unsanitized user-controlled input. | | Left shift by more than the type width (`java/lshift-larger-than-type-width`) | correctness | Finds left shifts of ints by 32 bits or more and left shifts of longs by 64 bits or more. Results are shown on LGTM by default. | | Suspicious date format (`java/suspicious-date-format`) | correctness | Finds date format patterns that use placeholders that are likely to be incorrect. | From 2dd368fd1f4c931a58d399d26aeef2b5ccbd02dc Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 31 Jan 2020 11:30:55 -0800 Subject: [PATCH 117/148] C++: add SSA test for void* buffer parameters --- .../ir/ssa/aliased_ssa_ir.expected | 53 +++++++++++++++++++ .../ir/ssa/aliased_ssa_ir_unsound.expected | 53 +++++++++++++++++++ cpp/ql/test/library-tests/ir/ssa/ssa.cpp | 7 +++ .../ir/ssa/unaliased_ssa_ir.expected | 50 +++++++++++++++++ .../ir/ssa/unaliased_ssa_ir_unsound.expected | 50 +++++++++++++++++ 5 files changed, 213 insertions(+) diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected index 15c631f727c..d2c1688c7de 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected @@ -1083,3 +1083,56 @@ ssa.cpp: # 239| v239_5(void) = UnmodeledUse : mu* # 239| v239_6(void) = AliasedUse : ~m244_5 # 239| v239_7(void) = ExitFunction : + +# 247| char* VoidStarIndirectParameters(char*, int) +# 247| Block 0 +# 247| v247_1(void) = EnterFunction : +# 247| m247_2(unknown) = AliasedDefinition : +# 247| mu247_3(unknown) = UnmodeledDefinition : +# 247| r247_4(glval) = VariableAddress[src] : +# 247| m247_5(char *) = InitializeParameter[src] : &:r247_4 +# 247| r247_6(char *) = Load : &:r247_4, m247_5 +# 247| m247_7(unknown) = InitializeIndirection[src] : &:r247_6 +# 247| r247_8(glval) = VariableAddress[size] : +# 247| m247_9(int) = InitializeParameter[size] : &:r247_8 +# 248| r248_1(glval) = VariableAddress[dst] : +# 248| r248_2(glval) = FunctionAddress[operator new[]] : +# 248| r248_3(glval) = VariableAddress[size] : +# 248| r248_4(int) = Load : &:r248_3, m247_9 +# 248| r248_5(unsigned long) = Convert : r248_4 +# 248| r248_6(unsigned long) = Constant[1] : +# 248| r248_7(unsigned long) = Mul : r248_5, r248_6 +# 248| r248_8(void *) = Call : func:r248_2, 0:r248_7 +# 248| m248_9(unknown) = ^CallSideEffect : ~m247_7 +# 248| m248_10(unknown) = Chi : total:m247_7, partial:m248_9 +# 248| r248_11(char *) = Convert : r248_8 +# 248| m248_12(char *) = Store : &:r248_1, r248_11 +# 249| r249_1(char) = Constant[97] : +# 249| r249_2(glval) = VariableAddress[src] : +# 249| r249_3(char *) = Load : &:r249_2, m247_5 +# 249| r249_4(glval) = CopyValue : r249_3 +# 249| m249_5(char) = Store : &:r249_4, r249_1 +# 249| m249_6(unknown) = Chi : total:m248_10, partial:m249_5 +# 250| r250_1(glval) = FunctionAddress[memcpy] : +# 250| r250_2(glval) = VariableAddress[dst] : +# 250| r250_3(char *) = Load : &:r250_2, m248_12 +# 250| r250_4(void *) = Convert : r250_3 +# 250| r250_5(glval) = VariableAddress[src] : +# 250| r250_6(char *) = Load : &:r250_5, m247_5 +# 250| r250_7(void *) = Convert : r250_6 +# 250| r250_8(glval) = VariableAddress[size] : +# 250| r250_9(int) = Load : &:r250_8, m247_9 +# 250| r250_10(void *) = Call : func:r250_1, 0:r250_4, 1:r250_7, 2:r250_9 +# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~m249_5 +# 250| m250_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r250_4, r250_9 +# 250| m250_13(unknown) = Chi : total:m249_6, partial:m250_12 +# 251| r251_1(glval) = VariableAddress[#return] : +# 251| r251_2(glval) = VariableAddress[dst] : +# 251| r251_3(char *) = Load : &:r251_2, m248_12 +# 251| m251_4(char *) = Store : &:r251_1, r251_3 +# 247| v247_10(void) = ReturnIndirection : &:r247_6, ~m250_13 +# 247| r247_11(glval) = VariableAddress[#return] : +# 247| v247_12(void) = ReturnValue : &:r247_11, m251_4 +# 247| v247_13(void) = UnmodeledUse : mu* +# 247| v247_14(void) = AliasedUse : ~m250_13 +# 247| v247_15(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected index ef8570bd1a5..3fd39a6b7a1 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected @@ -1078,3 +1078,56 @@ ssa.cpp: # 239| v239_5(void) = UnmodeledUse : mu* # 239| v239_6(void) = AliasedUse : ~m244_5 # 239| v239_7(void) = ExitFunction : + +# 247| char* VoidStarIndirectParameters(char*, int) +# 247| Block 0 +# 247| v247_1(void) = EnterFunction : +# 247| m247_2(unknown) = AliasedDefinition : +# 247| mu247_3(unknown) = UnmodeledDefinition : +# 247| r247_4(glval) = VariableAddress[src] : +# 247| m247_5(char *) = InitializeParameter[src] : &:r247_4 +# 247| r247_6(char *) = Load : &:r247_4, m247_5 +# 247| m247_7(unknown) = InitializeIndirection[src] : &:r247_6 +# 247| r247_8(glval) = VariableAddress[size] : +# 247| m247_9(int) = InitializeParameter[size] : &:r247_8 +# 248| r248_1(glval) = VariableAddress[dst] : +# 248| r248_2(glval) = FunctionAddress[operator new[]] : +# 248| r248_3(glval) = VariableAddress[size] : +# 248| r248_4(int) = Load : &:r248_3, m247_9 +# 248| r248_5(unsigned long) = Convert : r248_4 +# 248| r248_6(unsigned long) = Constant[1] : +# 248| r248_7(unsigned long) = Mul : r248_5, r248_6 +# 248| r248_8(void *) = Call : func:r248_2, 0:r248_7 +# 248| m248_9(unknown) = ^CallSideEffect : ~m247_2 +# 248| m248_10(unknown) = Chi : total:m247_2, partial:m248_9 +# 248| r248_11(char *) = Convert : r248_8 +# 248| m248_12(char *) = Store : &:r248_1, r248_11 +# 249| r249_1(char) = Constant[97] : +# 249| r249_2(glval) = VariableAddress[src] : +# 249| r249_3(char *) = Load : &:r249_2, m247_5 +# 249| r249_4(glval) = CopyValue : r249_3 +# 249| m249_5(char) = Store : &:r249_4, r249_1 +# 249| m249_6(unknown) = Chi : total:m247_7, partial:m249_5 +# 250| r250_1(glval) = FunctionAddress[memcpy] : +# 250| r250_2(glval) = VariableAddress[dst] : +# 250| r250_3(char *) = Load : &:r250_2, m248_12 +# 250| r250_4(void *) = Convert : r250_3 +# 250| r250_5(glval) = VariableAddress[src] : +# 250| r250_6(char *) = Load : &:r250_5, m247_5 +# 250| r250_7(void *) = Convert : r250_6 +# 250| r250_8(glval) = VariableAddress[size] : +# 250| r250_9(int) = Load : &:r250_8, m247_9 +# 250| r250_10(void *) = Call : func:r250_1, 0:r250_4, 1:r250_7, 2:r250_9 +# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~m249_5 +# 250| m250_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r250_4, r250_9 +# 250| m250_13(unknown) = Chi : total:m248_10, partial:m250_12 +# 251| r251_1(glval) = VariableAddress[#return] : +# 251| r251_2(glval) = VariableAddress[dst] : +# 251| r251_3(char *) = Load : &:r251_2, m248_12 +# 251| m251_4(char *) = Store : &:r251_1, r251_3 +# 247| v247_10(void) = ReturnIndirection : &:r247_6, ~m249_6 +# 247| r247_11(glval) = VariableAddress[#return] : +# 247| v247_12(void) = ReturnValue : &:r247_11, m251_4 +# 247| v247_13(void) = UnmodeledUse : mu* +# 247| v247_14(void) = AliasedUse : ~m250_13 +# 247| v247_15(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/ssa.cpp b/cpp/ql/test/library-tests/ir/ssa/ssa.cpp index 761289c5017..40a8017b9ef 100644 --- a/cpp/ql/test/library-tests/ir/ssa/ssa.cpp +++ b/cpp/ql/test/library-tests/ir/ssa/ssa.cpp @@ -243,3 +243,10 @@ void ExplicitConstructorCalls() { Constructible c2 = Constructible(2); c2.g(); } + +char *VoidStarIndirectParameters(char *src, int size) { + char *dst = new char[size]; + *src = 'a'; + memcpy(dst, src, size); + return dst; +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected index 83021d91c9c..b4d5e1833cf 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected @@ -1026,3 +1026,53 @@ ssa.cpp: # 239| v239_5(void) = UnmodeledUse : mu* # 239| v239_6(void) = AliasedUse : ~mu239_3 # 239| v239_7(void) = ExitFunction : + +# 247| char* VoidStarIndirectParameters(char*, int) +# 247| Block 0 +# 247| v247_1(void) = EnterFunction : +# 247| mu247_2(unknown) = AliasedDefinition : +# 247| mu247_3(unknown) = UnmodeledDefinition : +# 247| r247_4(glval) = VariableAddress[src] : +# 247| m247_5(char *) = InitializeParameter[src] : &:r247_4 +# 247| r247_6(char *) = Load : &:r247_4, m247_5 +# 247| mu247_7(unknown) = InitializeIndirection[src] : &:r247_6 +# 247| r247_8(glval) = VariableAddress[size] : +# 247| m247_9(int) = InitializeParameter[size] : &:r247_8 +# 248| r248_1(glval) = VariableAddress[dst] : +# 248| r248_2(glval) = FunctionAddress[operator new[]] : +# 248| r248_3(glval) = VariableAddress[size] : +# 248| r248_4(int) = Load : &:r248_3, m247_9 +# 248| r248_5(unsigned long) = Convert : r248_4 +# 248| r248_6(unsigned long) = Constant[1] : +# 248| r248_7(unsigned long) = Mul : r248_5, r248_6 +# 248| r248_8(void *) = Call : func:r248_2, 0:r248_7 +# 248| mu248_9(unknown) = ^CallSideEffect : ~mu247_3 +# 248| r248_10(char *) = Convert : r248_8 +# 248| m248_11(char *) = Store : &:r248_1, r248_10 +# 249| r249_1(char) = Constant[97] : +# 249| r249_2(glval) = VariableAddress[src] : +# 249| r249_3(char *) = Load : &:r249_2, m247_5 +# 249| r249_4(glval) = CopyValue : r249_3 +# 249| mu249_5(char) = Store : &:r249_4, r249_1 +# 250| r250_1(glval) = FunctionAddress[memcpy] : +# 250| r250_2(glval) = VariableAddress[dst] : +# 250| r250_3(char *) = Load : &:r250_2, m248_11 +# 250| r250_4(void *) = Convert : r250_3 +# 250| r250_5(glval) = VariableAddress[src] : +# 250| r250_6(char *) = Load : &:r250_5, m247_5 +# 250| r250_7(void *) = Convert : r250_6 +# 250| r250_8(glval) = VariableAddress[size] : +# 250| r250_9(int) = Load : &:r250_8, m247_9 +# 250| r250_10(void *) = Call : func:r250_1, 0:r250_4, 1:r250_7, 2:r250_9 +# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~mu247_3 +# 250| mu250_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r250_4, r250_9 +# 251| r251_1(glval) = VariableAddress[#return] : +# 251| r251_2(glval) = VariableAddress[dst] : +# 251| r251_3(char *) = Load : &:r251_2, m248_11 +# 251| m251_4(char *) = Store : &:r251_1, r251_3 +# 247| v247_10(void) = ReturnIndirection : &:r247_6, ~mu247_3 +# 247| r247_11(glval) = VariableAddress[#return] : +# 247| v247_12(void) = ReturnValue : &:r247_11, m251_4 +# 247| v247_13(void) = UnmodeledUse : mu* +# 247| v247_14(void) = AliasedUse : ~mu247_3 +# 247| v247_15(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected index 83021d91c9c..b4d5e1833cf 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected @@ -1026,3 +1026,53 @@ ssa.cpp: # 239| v239_5(void) = UnmodeledUse : mu* # 239| v239_6(void) = AliasedUse : ~mu239_3 # 239| v239_7(void) = ExitFunction : + +# 247| char* VoidStarIndirectParameters(char*, int) +# 247| Block 0 +# 247| v247_1(void) = EnterFunction : +# 247| mu247_2(unknown) = AliasedDefinition : +# 247| mu247_3(unknown) = UnmodeledDefinition : +# 247| r247_4(glval) = VariableAddress[src] : +# 247| m247_5(char *) = InitializeParameter[src] : &:r247_4 +# 247| r247_6(char *) = Load : &:r247_4, m247_5 +# 247| mu247_7(unknown) = InitializeIndirection[src] : &:r247_6 +# 247| r247_8(glval) = VariableAddress[size] : +# 247| m247_9(int) = InitializeParameter[size] : &:r247_8 +# 248| r248_1(glval) = VariableAddress[dst] : +# 248| r248_2(glval) = FunctionAddress[operator new[]] : +# 248| r248_3(glval) = VariableAddress[size] : +# 248| r248_4(int) = Load : &:r248_3, m247_9 +# 248| r248_5(unsigned long) = Convert : r248_4 +# 248| r248_6(unsigned long) = Constant[1] : +# 248| r248_7(unsigned long) = Mul : r248_5, r248_6 +# 248| r248_8(void *) = Call : func:r248_2, 0:r248_7 +# 248| mu248_9(unknown) = ^CallSideEffect : ~mu247_3 +# 248| r248_10(char *) = Convert : r248_8 +# 248| m248_11(char *) = Store : &:r248_1, r248_10 +# 249| r249_1(char) = Constant[97] : +# 249| r249_2(glval) = VariableAddress[src] : +# 249| r249_3(char *) = Load : &:r249_2, m247_5 +# 249| r249_4(glval) = CopyValue : r249_3 +# 249| mu249_5(char) = Store : &:r249_4, r249_1 +# 250| r250_1(glval) = FunctionAddress[memcpy] : +# 250| r250_2(glval) = VariableAddress[dst] : +# 250| r250_3(char *) = Load : &:r250_2, m248_11 +# 250| r250_4(void *) = Convert : r250_3 +# 250| r250_5(glval) = VariableAddress[src] : +# 250| r250_6(char *) = Load : &:r250_5, m247_5 +# 250| r250_7(void *) = Convert : r250_6 +# 250| r250_8(glval) = VariableAddress[size] : +# 250| r250_9(int) = Load : &:r250_8, m247_9 +# 250| r250_10(void *) = Call : func:r250_1, 0:r250_4, 1:r250_7, 2:r250_9 +# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~mu247_3 +# 250| mu250_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r250_4, r250_9 +# 251| r251_1(glval) = VariableAddress[#return] : +# 251| r251_2(glval) = VariableAddress[dst] : +# 251| r251_3(char *) = Load : &:r251_2, m248_11 +# 251| m251_4(char *) = Store : &:r251_1, r251_3 +# 247| v247_10(void) = ReturnIndirection : &:r247_6, ~mu247_3 +# 247| r247_11(glval) = VariableAddress[#return] : +# 247| v247_12(void) = ReturnValue : &:r247_11, m251_4 +# 247| v247_13(void) = UnmodeledUse : mu* +# 247| v247_14(void) = AliasedUse : ~mu247_3 +# 247| v247_15(void) = ExitFunction : From 3e2b0328b7522f18a623beb360a20d7d439197ee Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 31 Jan 2020 11:48:51 -0800 Subject: [PATCH 118/148] C++: update test expectations post-merge --- cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected | 2 +- .../test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected index d2c1688c7de..7ddc3c4f8c6 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected @@ -1123,7 +1123,7 @@ ssa.cpp: # 250| r250_8(glval) = VariableAddress[size] : # 250| r250_9(int) = Load : &:r250_8, m247_9 # 250| r250_10(void *) = Call : func:r250_1, 0:r250_4, 1:r250_7, 2:r250_9 -# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~m249_5 +# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~m249_6 # 250| m250_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r250_4, r250_9 # 250| m250_13(unknown) = Chi : total:m249_6, partial:m250_12 # 251| r251_1(glval) = VariableAddress[#return] : diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected index 3fd39a6b7a1..001b3629aa2 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected @@ -1118,7 +1118,7 @@ ssa.cpp: # 250| r250_8(glval) = VariableAddress[size] : # 250| r250_9(int) = Load : &:r250_8, m247_9 # 250| r250_10(void *) = Call : func:r250_1, 0:r250_4, 1:r250_7, 2:r250_9 -# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~m249_5 +# 250| v250_11(void) = ^SizedBufferReadSideEffect[1] : &:r250_7, r250_9, ~m249_6 # 250| m250_12(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r250_4, r250_9 # 250| m250_13(unknown) = Chi : total:m248_10, partial:m250_12 # 251| r251_1(glval) = VariableAddress[#return] : From e2da98ae2419e3295fc7d37fc16544eb01f00176 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Fri, 31 Jan 2020 20:58:53 +0100 Subject: [PATCH 119/148] C++: Accept autoformat and test changes --- .../cpp/ir/dataflow/DefaultTaintTracking.qll | 16 ++++++++-------- .../DefaultTaintTracking/tainted.expected | 4 ++++ .../DefaultTaintTracking/test_diff.expected | 4 ++-- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll index a006246fc33..fea47e1b5bd 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -154,8 +154,8 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) { // Until we have from through indirections across calls, we'll take flow out // of the parameter and into its indirection. exists(IRFunction f, Parameter parameter | - initializeParameter(f, parameter, i1) and - initializeIndirection(f, parameter, i2) + i1 = getInitializeParameter(f, parameter) and + i2 = getInitializeIndirection(f, parameter) ) or // Until we have flow through indirections across calls, we'll take flow out @@ -193,15 +193,15 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) { } pragma[noinline] -private predicate initializeIndirection(IRFunction f, Parameter p, InitializeIndirectionInstruction instr) { - instr.getParameter() = p and - instr.getEnclosingIRFunction() = f +private InitializeIndirectionInstruction getInitializeIndirection(IRFunction f, Parameter p) { + result.getParameter() = p and + result.getEnclosingIRFunction() = f } pragma[noinline] -private predicate initializeParameter(IRFunction f, Parameter p, InitializeParameterInstruction instr) { - instr.getParameter() = p and - instr.getEnclosingIRFunction() = f +private InitializeParameterInstruction getInitializeParameter(IRFunction f, Parameter p) { + result.getParameter() = p and + result.getEnclosingIRFunction() = f } /** diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected index 92948eeffc6..4260d275e99 100644 --- a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected +++ b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/tainted.expected @@ -21,14 +21,18 @@ | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:22:8:22:33 | (const char *)... | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:22:20:22:25 | call to getenv | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:22:20:22:32 | (const char *)... | +| defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:24:8:24:10 | (const char *)... | +| defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:24:8:24:10 | array to pointer conversion | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:24:8:24:10 | buf | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | test_diff.cpp:1:11:1:20 | p#0 | +| defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:31:40:31:53 | dotted_address | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:32:11:32:26 | p#0 | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:38:11:38:21 | env_pointer | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:38:25:38:30 | call to getenv | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:38:25:38:37 | (void *)... | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:22:39:22 | a | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:26:39:34 | call to inet_addr | +| defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:36:39:61 | (const char *)... | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:50:39:61 | & ... | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:40:10:40:10 | a | | defaulttainttracking.cpp:64:10:64:15 | call to getenv | defaulttainttracking.cpp:9:11:9:20 | p#0 | diff --git a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected index c557f43f1c7..37a8e2c8d4c 100644 --- a/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/DefaultTaintTracking/test_diff.expected @@ -5,8 +5,8 @@ | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:3:21:3:22 | s1 | AST only | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:21:8:21:10 | buf | AST only | | defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:22:15:22:17 | buf | AST only | -| defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:31:40:31:53 | dotted_address | AST only | -| defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:36:39:61 | (const char *)... | AST only | +| defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:24:8:24:10 | (const char *)... | IR only | +| defaulttainttracking.cpp:22:20:22:25 | call to getenv | defaulttainttracking.cpp:24:8:24:10 | array to pointer conversion | IR only | | defaulttainttracking.cpp:38:25:38:30 | call to getenv | defaulttainttracking.cpp:39:51:39:61 | env_pointer | AST only | | defaulttainttracking.cpp:64:10:64:15 | call to getenv | defaulttainttracking.cpp:52:24:52:24 | p | IR only | | test_diff.cpp:104:12:104:15 | argv | test_diff.cpp:104:11:104:20 | (...) | IR only | From 5ff958a9cf367227571afdb3083d56f153479883 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 3 Feb 2020 09:39:41 +0100 Subject: [PATCH 120/148] fix compilation of PrototypePollutionUtility after refactor --- javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index 2d66dd03a99..d55a40422a1 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -113,7 +113,7 @@ class EntriesEnumeratedPropName extends EnumeratedPropName { * Holds if the properties of `node` are enumerated locally. */ predicate arePropertiesEnumerated(DataFlow::SourceNode node) { - node = any(EnumeratedPropName name).getASourceObjectRef() + node = AccessPath::getAnAliasedSourceNode(any(EnumeratedPropName name).getSourceObject()) } /** From abb95135c16d7200752bef184a0066f6c7c0b5ec Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 30 Jan 2020 11:40:57 +0000 Subject: [PATCH 121/148] JS: Add UnresolvableImport metric --- .../analysis-quality/UnresolvableImports.ql | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql diff --git a/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql b/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql new file mode 100644 index 00000000000..a2a35e8f11c --- /dev/null +++ b/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql @@ -0,0 +1,18 @@ +/** + * @name Unresolvable imports + * @description The number of imports that could not be resolved to a module. + * @kind metric + * @metricType project + * @metricAggregate sum + * @tags meta + * @id js/meta/unresolvable-imports + */ + +import javascript +import CallGraphQuality + +Import unresolvableImport() { + not exists(result.getImportedModule()) +} + +select projectRoot(), count(unresolvableImport()) From 9abf5f06e6dc9a8d2524dea90e31f726b698f5f6 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 28 Jan 2020 15:14:08 +0000 Subject: [PATCH 122/148] TS: Resolve imports using TypeScript symbols --- change-notes/1.24/analysis-javascript.md | 4 +++- .../extractor/lib/typescript/src/ast_extractor.ts | 9 +++++++-- .../src/com/semmle/js/ast/ImportDeclaration.java | 15 ++++++++++++++- .../src/com/semmle/js/extractor/ASTExtractor.java | 8 ++++++-- .../semmle/js/parser/TypeScriptASTConverter.java | 8 ++++++-- .../semmle/ts/ast/ExternalModuleReference.java | 13 ++++++++++++- javascript/ql/src/semmle/javascript/Modules.qll | 13 ++++++++++++- javascript/ql/src/semmlecode.javascript.dbscheme | 2 +- .../TypeScript/PathMapping/Imports.expected | 11 +++++++++++ .../TypeScript/PathMapping/Imports.ql | 8 ++++++++ .../TypeScript/PathMapping/src/lib/foo.ts | 3 +++ .../TypeScript/PathMapping/test/test_foo.ts | 6 ++++++ .../TypeScript/PathMapping/tsconfig.json | 9 +++++++++ 13 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.expected create mode 100644 javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql create mode 100644 javascript/ql/test/library-tests/TypeScript/PathMapping/src/lib/foo.ts create mode 100644 javascript/ql/test/library-tests/TypeScript/PathMapping/test/test_foo.ts create mode 100644 javascript/ql/test/library-tests/TypeScript/PathMapping/tsconfig.json diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index b19d0e49b75..91d21fa18ce 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -7,7 +7,9 @@ * Imports with the `.js` extension can now be resolved to a TypeScript file, when the import refers to a file generated by TypeScript. -- The analysis of sanitizer guards has improved, leading to fewer false-positive results from the security queries. +* Imports that rely on path-mappings from a `tsconfig.json` file can now be resolved. + +* The analysis of sanitizer guards has improved, leading to fewer false-positive results from the security queries. * Support for the following frameworks and libraries has been improved: - [react](https://www.npmjs.com/package/react) diff --git a/javascript/extractor/lib/typescript/src/ast_extractor.ts b/javascript/extractor/lib/typescript/src/ast_extractor.ts index 4ef306db0ca..c1415dbd294 100644 --- a/javascript/extractor/lib/typescript/src/ast_extractor.ts +++ b/javascript/extractor/lib/typescript/src/ast_extractor.ts @@ -251,8 +251,13 @@ export function augmentAst(ast: AugmentedSourceFile, code: string, project: Proj } } } - if (isNamedNodeWithSymbol(node)) { - let symbol = typeChecker.getSymbolAtLocation(node.name); + let symbolNode = + isNamedNodeWithSymbol(node) ? node.name : + ts.isImportDeclaration(node) ? node.moduleSpecifier : + ts.isExternalModuleReference(node) ? node.expression : + null; + if (symbolNode != null) { + let symbol = typeChecker.getSymbolAtLocation(symbolNode); if (symbol != null) { node.$symbol = typeTable.getSymbolId(symbol); } diff --git a/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java b/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java index a63b5fdbba1..1e538c1bb74 100644 --- a/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java +++ b/javascript/extractor/src/com/semmle/js/ast/ImportDeclaration.java @@ -1,5 +1,6 @@ package com.semmle.js.ast; +import com.semmle.ts.ast.INodeWithSymbol; import java.util.List; /** @@ -14,13 +15,15 @@ import java.util.List; * import "m"; * */ -public class ImportDeclaration extends Statement { +public class ImportDeclaration extends Statement implements INodeWithSymbol { /** List of import specifiers detailing how declarations are imported; may be empty. */ private final List specifiers; /** The module from which declarations are imported. */ private final Literal source; + private int symbol = -1; + public ImportDeclaration(SourceLocation loc, List specifiers, Literal source) { super("ImportDeclaration", loc); this.specifiers = specifiers; @@ -39,4 +42,14 @@ public class ImportDeclaration extends Statement { public R accept(Visitor v, C c) { return v.visit(this, c); } + + @Override + public int getSymbol() { + return this.symbol; + } + + @Override + public void setSymbol(int symbol) { + this.symbol = symbol; + } } diff --git a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java index 4bc7d0fbdd3..6af90d562d8 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java +++ b/javascript/extractor/src/com/semmle/js/extractor/ASTExtractor.java @@ -1555,6 +1555,7 @@ public class ASTExtractor { Label lbl = super.visit(nd, c); visit(nd.getSource(), lbl, -1); visitAll(nd.getSpecifiers(), lbl); + emitNodeSymbol(nd, lbl); return lbl; } @@ -1705,6 +1706,7 @@ public class ASTExtractor { public Label visit(ExternalModuleReference nd, Context c) { Label key = super.visit(nd, c); visit(nd.getExpression(), key, 0); + emitNodeSymbol(nd, key); return key; } @@ -2061,12 +2063,14 @@ public class ASTExtractor { @Override public Label visit(AssignmentPattern nd, Context c) { - additionalErrors.add(new ParseError("Unexpected assignment pattern.", nd.getLoc().getStart())); + additionalErrors.add( + new ParseError("Unexpected assignment pattern.", nd.getLoc().getStart())); return super.visit(nd, c); } } - public List extract(Node root, Platform platform, SourceType sourceType, int toplevelKind) { + public List extract( + Node root, Platform platform, SourceType sourceType, int toplevelKind) { lexicalExtractor.getMetrics().startPhase(ExtractionPhase.ASTExtractor_extract); trapwriter.addTuple("toplevels", toplevelLabel, toplevelKind); locationManager.emitNodeLocation(root, toplevelLabel); diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java index 733eba38df1..1b0e5421497 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptASTConverter.java @@ -1202,7 +1202,9 @@ public class TypeScriptASTConverter { private Node convertExternalModuleReference(JsonObject node, SourceLocation loc) throws ParseError { - return new ExternalModuleReference(loc, convertChild(node, "expression")); + ExternalModuleReference moduleRef = new ExternalModuleReference(loc, convertChild(node, "expression")); + attachSymbolInformation(moduleRef, node); + return moduleRef; } private Node convertFalseKeyword(SourceLocation loc) { @@ -1366,7 +1368,9 @@ public class TypeScriptASTConverter { } } } - return new ImportDeclaration(loc, specifiers, src); + ImportDeclaration importDecl = new ImportDeclaration(loc, specifiers, src); + attachSymbolInformation(importDecl, node); + return importDecl; } private Node convertImportEqualsDeclaration(JsonObject node, SourceLocation loc) diff --git a/javascript/extractor/src/com/semmle/ts/ast/ExternalModuleReference.java b/javascript/extractor/src/com/semmle/ts/ast/ExternalModuleReference.java index 48b03ae5a16..022bf3b2aa7 100644 --- a/javascript/extractor/src/com/semmle/ts/ast/ExternalModuleReference.java +++ b/javascript/extractor/src/com/semmle/ts/ast/ExternalModuleReference.java @@ -4,8 +4,9 @@ import com.semmle.js.ast.Expression; import com.semmle.js.ast.SourceLocation; import com.semmle.js.ast.Visitor; -public class ExternalModuleReference extends Expression { +public class ExternalModuleReference extends Expression implements INodeWithSymbol { private final Expression expression; + private int symbol = -1; public ExternalModuleReference(SourceLocation loc, Expression expression) { super("ExternalModuleReference", loc); @@ -20,4 +21,14 @@ public class ExternalModuleReference extends Expression { public R accept(Visitor v, C c) { return v.visit(this, c); } + + @Override + public int getSymbol() { + return this.symbol; + } + + @Override + public void setSymbol(int symbol) { + this.symbol = symbol; + } } diff --git a/javascript/ql/src/semmle/javascript/Modules.qll b/javascript/ql/src/semmle/javascript/Modules.qll index c18637f866a..26b2331afcf 100644 --- a/javascript/ql/src/semmle/javascript/Modules.qll +++ b/javascript/ql/src/semmle/javascript/Modules.qll @@ -148,6 +148,16 @@ abstract class Import extends ASTNode { ) } + /** + * Gets the imported module, as determined by the TypeScript compiler, if any. + */ + private Module resolveFromTypeScriptSymbol() { + exists(CanonicalName symbol | + ast_node_symbol(this, symbol) and + ast_node_symbol(result, symbol) + ) + } + /** * Gets the module this import refers to. * @@ -162,7 +172,8 @@ abstract class Import extends ASTNode { else ( result = resolveAsProvidedModule() or result = resolveImportedPath() or - result = resolveFromTypeRoot() + result = resolveFromTypeRoot() or + result = resolveFromTypeScriptSymbol() ) } diff --git a/javascript/ql/src/semmlecode.javascript.dbscheme b/javascript/ql/src/semmlecode.javascript.dbscheme index 96b0a386b6f..dad09eeeff5 100644 --- a/javascript/ql/src/semmlecode.javascript.dbscheme +++ b/javascript/ql/src/semmlecode.javascript.dbscheme @@ -688,7 +688,7 @@ case @symbol.kind of ; @type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype; -@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr; +@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr | @importdeclaration | @externalmodulereference; ast_node_symbol( unique int node: @ast_node_with_symbol ref, diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.expected b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.expected new file mode 100644 index 00000000000..c608ea2c7b6 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.expected @@ -0,0 +1,11 @@ +symbols +| src/lib/foo.ts:1:1:4:0 | | library-tests/TypeScript/PathMapping/src/lib/foo.ts | +| src/lib/foo.ts:1:8:3:1 | functio ... 123;\\n} | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts | +| test/test_foo.ts:1:1:1:28 | import ... @/foo"; | library-tests/TypeScript/PathMapping/src/lib/foo.ts | +| test/test_foo.ts:1:1:7:0 | | library-tests/TypeScript/PathMapping/test/test_foo.ts | +| test/test_foo.ts:2:17:2:32 | require("@/foo") | library-tests/TypeScript/PathMapping/src/lib/foo.ts | +| test/test_foo.ts:4:1:4:5 | foo() | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts | +| test/test_foo.ts:6:1:6:12 | foolib.foo() | foo in library-tests/TypeScript/PathMapping/src/lib/foo.ts | +#select +| test/test_foo.ts:1:1:1:28 | import ... @/foo"; | src/lib/foo.ts:1:1:4:0 | | +| test/test_foo.ts:2:17:2:32 | require("@/foo") | src/lib/foo.ts:1:1:4:0 | | diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql new file mode 100644 index 00000000000..bd272eee884 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql @@ -0,0 +1,8 @@ +import javascript + +query predicate symbols(ASTNode astNode, CanonicalName symbol) { + ast_node_symbol(astNode, symbol) +} + +from Import imprt +select imprt, imprt.getImportedModule() diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/src/lib/foo.ts b/javascript/ql/test/library-tests/TypeScript/PathMapping/src/lib/foo.ts new file mode 100644 index 00000000000..0ef2bf692fd --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/src/lib/foo.ts @@ -0,0 +1,3 @@ +export function foo() { + return 123; +} diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/test/test_foo.ts b/javascript/ql/test/library-tests/TypeScript/PathMapping/test/test_foo.ts new file mode 100644 index 00000000000..d30f56c53e7 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/test/test_foo.ts @@ -0,0 +1,6 @@ +import { foo } from "@/foo"; +import foolib = require("@/foo"); + +foo(); + +foolib.foo(); diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/tsconfig.json b/javascript/ql/test/library-tests/TypeScript/PathMapping/tsconfig.json new file mode 100644 index 00000000000..476d1ad1ee5 --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/tsconfig.json @@ -0,0 +1,9 @@ +{ + "include": ["."], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/lib/*"] + } + } +} From 513854a6082f2442421bafaceadddf8ec4106486 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 28 Jan 2020 15:53:45 +0000 Subject: [PATCH 123/148] TS: Add upgrade script --- .../old.dbscheme | 1186 +++++++++++++++++ .../semmlecode.javascript.dbscheme | 1186 +++++++++++++++++ .../upgrade.properties | 2 + 3 files changed, 2374 insertions(+) create mode 100644 javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/old.dbscheme create mode 100644 javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/semmlecode.javascript.dbscheme create mode 100644 javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/upgrade.properties diff --git a/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/old.dbscheme b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/old.dbscheme new file mode 100644 index 00000000000..96b0a386b6f --- /dev/null +++ b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/old.dbscheme @@ -0,0 +1,1186 @@ +/*** Standard fragments ***/ + +/** Files and folders **/ + +@location = @location_default; + +locations_default(unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref + ); + +@sourceline = @locatable; + +numlines(int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref + ); + + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files(unique int id: @file, + varchar(900) name: string ref, + varchar(900) simple: string ref, + varchar(900) ext: string ref, + int fromSource: int ref); + +folders(unique int id: @folder, + varchar(900) name: string ref, + varchar(900) simple: string ref); + + +@container = @folder | @file ; + + +containerparent(int parent: @container ref, + unique int child: @container ref); + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +/** Version control data **/ + +svnentries( + int id : @svnentry, + varchar(500) revision : string ref, + varchar(500) author : string ref, + date revisionDate : date ref, + int changeSize : int ref +); + +svnaffectedfiles( + int id : @svnentry ref, + int file : @file ref, + varchar(500) action : string ref +); + +svnentrymsg( + int id : @svnentry ref, + varchar(500) message : string ref +); + +svnchurn( + int commit : @svnentry ref, + int file : @file ref, + int addedLines : int ref, + int deletedLines : int ref +); + + +/*** JavaScript-specific part ***/ + +filetype( + int file: @file ref, + string filetype: string ref +) + +// top-level code fragments +toplevels (unique int id: @toplevel, + int kind: int ref); + +isExterns (int toplevel: @toplevel ref); + +case @toplevel.kind of + 0 = @script +| 1 = @inline_script +| 2 = @event_handler +| 3 = @javascript_url; + +isModule (int tl: @toplevel ref); +isNodejs (int tl: @toplevel ref); +isES2015Module (int tl: @toplevel ref); +isClosureModule (int tl: @toplevel ref); + +// statements +#keyset[parent, idx] +stmts (unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +stmtContainers (unique int stmt: @stmt ref, + int container: @stmt_container ref); + +jumpTargets (unique int jump: @stmt ref, + int target: @stmt ref); + +@stmtparent = @stmt | @toplevel | @functionexpr | @arrowfunctionexpr; +@stmt_container = @toplevel | @function | @namespacedeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration; + +case @stmt.kind of + 0 = @emptystmt +| 1 = @blockstmt +| 2 = @exprstmt +| 3 = @ifstmt +| 4 = @labeledstmt +| 5 = @breakstmt +| 6 = @continuestmt +| 7 = @withstmt +| 8 = @switchstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @trystmt +| 12 = @whilestmt +| 13 = @dowhilestmt +| 14 = @forstmt +| 15 = @forinstmt +| 16 = @debuggerstmt +| 17 = @functiondeclstmt +| 18 = @vardeclstmt +| 19 = @case +| 20 = @catchclause +| 21 = @forofstmt +| 22 = @constdeclstmt +| 23 = @letstmt +| 24 = @legacy_letstmt +| 25 = @foreachstmt +| 26 = @classdeclstmt +| 27 = @importdeclaration +| 28 = @exportalldeclaration +| 29 = @exportdefaultdeclaration +| 30 = @exportnameddeclaration +| 31 = @namespacedeclaration +| 32 = @importequalsdeclaration +| 33 = @exportassigndeclaration +| 34 = @interfacedeclaration +| 35 = @typealiasdeclaration +| 36 = @enumdeclaration +| 37 = @externalmoduledeclaration +| 38 = @exportasnamespacedeclaration +| 39 = @globalaugmentationdeclaration +; + +@declstmt = @vardeclstmt | @constdeclstmt | @letstmt | @legacy_letstmt; + +@exportdeclaration = @exportalldeclaration | @exportdefaultdeclaration | @exportnameddeclaration; + +@namespacedefinition = @namespacedeclaration | @enumdeclaration; +@typedefinition = @classdefinition | @interfacedeclaration | @enumdeclaration | @typealiasdeclaration | @enum_member; + +isInstantiated(unique int decl: @namespacedeclaration ref); + +@declarablenode = @declstmt | @namespacedeclaration | @classdeclstmt | @functiondeclstmt | @enumdeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration | @field; +hasDeclareKeyword(unique int stmt: @declarablenode ref); + +isForAwaitOf(unique int forof: @forofstmt ref); + +// expressions +#keyset[parent, idx] +exprs (unique int id: @expr, + int kind: int ref, + int parent: @exprparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @exprortype ref); + +enclosingStmt (unique int expr: @exprortype ref, + int stmt: @stmt ref); + +exprContainers (unique int expr: @exprortype ref, + int container: @stmt_container ref); + +arraySize (unique int ae: @arraylike ref, + int sz: int ref); + +isDelegating (int yield: @yieldexpr ref); + +@exprorstmt = @expr | @stmt; +@exprortype = @expr | @typeexpr; +@exprparent = @exprorstmt | @property | @functiontypeexpr; +@arraylike = @arrayexpr | @arraypattern; +@type_annotation = @typeexpr | @jsdoc_type_expr; + +case @expr.kind of + 0 = @label +| 1 = @nullliteral +| 2 = @booleanliteral +| 3 = @numberliteral +| 4 = @stringliteral +| 5 = @regexpliteral +| 6 = @thisexpr +| 7 = @arrayexpr +| 8 = @objexpr +| 9 = @functionexpr +| 10 = @seqexpr +| 11 = @conditionalexpr +| 12 = @newexpr +| 13 = @callexpr +| 14 = @dotexpr +| 15 = @indexexpr +| 16 = @negexpr +| 17 = @plusexpr +| 18 = @lognotexpr +| 19 = @bitnotexpr +| 20 = @typeofexpr +| 21 = @voidexpr +| 22 = @deleteexpr +| 23 = @eqexpr +| 24 = @neqexpr +| 25 = @eqqexpr +| 26 = @neqqexpr +| 27 = @ltexpr +| 28 = @leexpr +| 29 = @gtexpr +| 30 = @geexpr +| 31 = @lshiftexpr +| 32 = @rshiftexpr +| 33 = @urshiftexpr +| 34 = @addexpr +| 35 = @subexpr +| 36 = @mulexpr +| 37 = @divexpr +| 38 = @modexpr +| 39 = @bitorexpr +| 40 = @xorexpr +| 41 = @bitandexpr +| 42 = @inexpr +| 43 = @instanceofexpr +| 44 = @logandexpr +| 45 = @logorexpr +| 47 = @assignexpr +| 48 = @assignaddexpr +| 49 = @assignsubexpr +| 50 = @assignmulexpr +| 51 = @assigndivexpr +| 52 = @assignmodexpr +| 53 = @assignlshiftexpr +| 54 = @assignrshiftexpr +| 55 = @assignurshiftexpr +| 56 = @assignorexpr +| 57 = @assignxorexpr +| 58 = @assignandexpr +| 59 = @preincexpr +| 60 = @postincexpr +| 61 = @predecexpr +| 62 = @postdecexpr +| 63 = @parexpr +| 64 = @vardeclarator +| 65 = @arrowfunctionexpr +| 66 = @spreadelement +| 67 = @arraypattern +| 68 = @objectpattern +| 69 = @yieldexpr +| 70 = @taggedtemplateexpr +| 71 = @templateliteral +| 72 = @templateelement +| 73 = @arraycomprehensionexpr +| 74 = @generatorexpr +| 75 = @forincomprehensionblock +| 76 = @forofcomprehensionblock +| 77 = @legacy_letexpr +| 78 = @vardecl +| 79 = @proper_varaccess +| 80 = @classexpr +| 81 = @superexpr +| 82 = @newtargetexpr +| 83 = @namedimportspecifier +| 84 = @importdefaultspecifier +| 85 = @importnamespacespecifier +| 86 = @namedexportspecifier +| 87 = @expexpr +| 88 = @assignexpexpr +| 89 = @jsxelement +| 90 = @jsxqualifiedname +| 91 = @jsxemptyexpr +| 92 = @awaitexpr +| 93 = @functionsentexpr +| 94 = @decorator +| 95 = @exportdefaultspecifier +| 96 = @exportnamespacespecifier +| 97 = @bindexpr +| 98 = @externalmodulereference +| 99 = @dynamicimport +| 100 = @expressionwithtypearguments +| 101 = @prefixtypeassertion +| 102 = @astypeassertion +| 103 = @export_varaccess +| 104 = @decorator_list +| 105 = @non_null_assertion +| 106 = @bigintliteral +| 107 = @nullishcoalescingexpr +| 108 = @e4x_xml_anyname +| 109 = @e4x_xml_static_attribute_selector +| 110 = @e4x_xml_dynamic_attribute_selector +| 111 = @e4x_xml_filter_expression +| 112 = @e4x_xml_static_qualident +| 113 = @e4x_xml_dynamic_qualident +| 114 = @e4x_xml_dotdotexpr +| 115 = @importmetaexpr +; + +@varaccess = @proper_varaccess | @export_varaccess; +@varref = @vardecl | @varaccess; + +@identifier = @label | @varref | @typeidentifier; + +@literal = @nullliteral | @booleanliteral | @numberliteral | @stringliteral | @regexpliteral | @bigintliteral; + +@propaccess = @dotexpr | @indexexpr; + +@invokeexpr = @newexpr | @callexpr; + +@unaryexpr = @negexpr | @plusexpr | @lognotexpr | @bitnotexpr | @typeofexpr | @voidexpr | @deleteexpr | @spreadelement; + +@equalitytest = @eqexpr | @neqexpr | @eqqexpr | @neqqexpr; + +@comparison = @equalitytest | @ltexpr | @leexpr | @gtexpr | @geexpr; + +@binaryexpr = @comparison | @lshiftexpr | @rshiftexpr | @urshiftexpr | @addexpr | @subexpr | @mulexpr | @divexpr | @modexpr | @expexpr | @bitorexpr | @xorexpr | @bitandexpr | @inexpr | @instanceofexpr | @logandexpr | @logorexpr | @nullishcoalescingexpr; + +@assignment = @assignexpr | @assignaddexpr | @assignsubexpr | @assignmulexpr | @assigndivexpr | @assignmodexpr | @assignexpexpr | @assignlshiftexpr | @assignrshiftexpr | @assignurshiftexpr | @assignorexpr | @assignxorexpr | @assignandexpr; + +@updateexpr = @preincexpr | @postincexpr | @predecexpr | @postdecexpr; + +@pattern = @varref | @arraypattern | @objectpattern; + +@comprehensionexpr = @arraycomprehensionexpr | @generatorexpr; + +@comprehensionblock = @forincomprehensionblock | @forofcomprehensionblock; + +@importspecifier = @namedimportspecifier | @importdefaultspecifier | @importnamespacespecifier; + +@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier; + +@typeassertion = @astypeassertion | @prefixtypeassertion; + +@classdefinition = @classdeclstmt | @classexpr; +@interfacedefinition = @interfacedeclaration | @interfacetypeexpr; +@classorinterface = @classdefinition | @interfacedefinition; + +@lexical_decl = @vardecl | @typedecl; +@lexical_access = @varaccess | @localtypeaccess | @localvartypeaccess | @localnamespaceaccess; +@lexical_ref = @lexical_decl | @lexical_access; + +@e4x_xml_attribute_selector = @e4x_xml_static_attribute_selector | @e4x_xml_dynamic_attribute_selector; +@e4x_xml_qualident = @e4x_xml_static_qualident | @e4x_xml_dynamic_qualident; + +// scopes +scopes (unique int id: @scope, + int kind: int ref); + +case @scope.kind of + 0 = @globalscope +| 1 = @functionscope +| 2 = @catchscope +| 3 = @modulescope +| 4 = @blockscope +| 5 = @forscope +| 6 = @forinscope // for-of scopes work the same as for-in scopes +| 7 = @comprehensionblockscope +| 8 = @classexprscope +| 9 = @namespacescope +| 10 = @classdeclscope +| 11 = @interfacescope +| 12 = @typealiasscope +| 13 = @mappedtypescope +| 14 = @enumscope +| 15 = @externalmodulescope +| 16 = @conditionaltypescope; + +scopenodes (unique int node: @ast_node ref, + int scope: @scope ref); + +scopenesting (unique int inner: @scope ref, + int outer: @scope ref); + +// functions +@function = @functiondeclstmt | @functionexpr | @arrowfunctionexpr; + +@parameterized = @function | @catchclause; +@type_parameterized = @function | @classorinterface | @typealiasdeclaration | @mappedtypeexpr | @infertypeexpr; + +isGenerator (int fun: @function ref); +hasRestParameter (int fun: @function ref); +isAsync (int fun: @function ref); + +// variables and lexically scoped type names +#keyset[scope, name] +variables (unique int id: @variable, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_type_names (unique int id: @local_type_name, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_namespace_names (unique int id: @local_namespace_name, + varchar(900) name: string ref, + int scope: @scope ref); + +isArgumentsObject (int id: @variable ref); + +@lexical_name = @variable | @local_type_name | @local_namespace_name; + +@bind_id = @varaccess | @localvartypeaccess; +bind (unique int id: @bind_id ref, + int decl: @variable ref); + +decl (unique int id: @vardecl ref, + int decl: @variable ref); + +@typebind_id = @localtypeaccess | @export_varaccess; +typebind (unique int id: @typebind_id ref, + int decl: @local_type_name ref); + +@typedecl_id = @typedecl | @vardecl; +typedecl (unique int id: @typedecl_id ref, + int decl: @local_type_name ref); + +namespacedecl (unique int id: @vardecl ref, + int decl: @local_namespace_name ref); + +@namespacebind_id = @localnamespaceaccess | @export_varaccess; +namespacebind (unique int id: @namespacebind_id ref, + int decl: @local_namespace_name ref); + + +// properties in object literals, property patterns in object patterns, and method declarations in classes +#keyset[parent, index] +properties (unique int id: @property, + int parent: @property_parent ref, + int index: int ref, + int kind: int ref, + varchar(900) tostring: string ref); + +case @property.kind of + 0 = @value_property +| 1 = @property_getter +| 2 = @property_setter +| 3 = @jsx_attribute +| 4 = @function_call_signature +| 5 = @constructor_call_signature +| 6 = @index_signature +| 7 = @enum_member +| 8 = @proper_field +| 9 = @parameter_field +; + +@property_parent = @objexpr | @objectpattern | @classdefinition | @jsxelement | @interfacedefinition | @enumdeclaration; +@property_accessor = @property_getter | @property_setter; +@call_signature = @function_call_signature | @constructor_call_signature; +@field = @proper_field | @parameter_field; +@field_or_vardeclarator = @field | @vardeclarator; + +isComputed (int id: @property ref); +isMethod (int id: @property ref); +isStatic (int id: @property ref); +isAbstractMember (int id: @property ref); +isConstEnum (int id: @enumdeclaration ref); +isAbstractClass (int id: @classdeclstmt ref); + +hasPublicKeyword (int id: @property ref); +hasPrivateKeyword (int id: @property ref); +hasProtectedKeyword (int id: @property ref); +hasReadonlyKeyword (int id: @property ref); +isOptionalMember (int id: @property ref); +hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator ref); +isOptionalParameterDeclaration (unique int parameter: @pattern ref); + +#keyset[constructor, param_index] +parameter_fields( + unique int field: @parameter_field ref, + int constructor: @functionexpr ref, + int param_index: int ref +); + +// types +#keyset[parent, idx] +typeexprs ( + unique int id: @typeexpr, + int kind: int ref, + int parent: @typeexpr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref +); + +case @typeexpr.kind of + 0 = @localtypeaccess +| 1 = @typedecl +| 2 = @keywordtypeexpr +| 3 = @stringliteraltypeexpr +| 4 = @numberliteraltypeexpr +| 5 = @booleanliteraltypeexpr +| 6 = @arraytypeexpr +| 7 = @uniontypeexpr +| 8 = @indexedaccesstypeexpr +| 9 = @intersectiontypeexpr +| 10 = @parenthesizedtypeexpr +| 11 = @tupletypeexpr +| 12 = @keyoftypeexpr +| 13 = @qualifiedtypeaccess +| 14 = @generictypeexpr +| 15 = @typelabel +| 16 = @typeoftypeexpr +| 17 = @localvartypeaccess +| 18 = @qualifiedvartypeaccess +| 19 = @thisvartypeaccess +| 20 = @predicatetypeexpr +| 21 = @interfacetypeexpr +| 22 = @typeparameter +| 23 = @plainfunctiontypeexpr +| 24 = @constructortypeexpr +| 25 = @localnamespaceaccess +| 26 = @qualifiednamespaceaccess +| 27 = @mappedtypeexpr +| 28 = @conditionaltypeexpr +| 29 = @infertypeexpr +| 30 = @importtypeaccess +| 31 = @importnamespaceaccess +| 32 = @importvartypeaccess +| 33 = @optionaltypeexpr +| 34 = @resttypeexpr +| 35 = @bigintliteraltypeexpr +| 36 = @readonlytypeexpr +; + +@typeref = @typeaccess | @typedecl; +@typeidentifier = @typedecl | @localtypeaccess | @typelabel | @localvartypeaccess | @localnamespaceaccess; +@typeexpr_parent = @expr | @stmt | @property | @typeexpr; +@literaltypeexpr = @stringliteraltypeexpr | @numberliteraltypeexpr | @booleanliteraltypeexpr | @bigintliteraltypeexpr; +@typeaccess = @localtypeaccess | @qualifiedtypeaccess | @importtypeaccess; +@vartypeaccess = @localvartypeaccess | @qualifiedvartypeaccess | @thisvartypeaccess | @importvartypeaccess; +@namespaceaccess = @localnamespaceaccess | @qualifiednamespaceaccess | @importnamespaceaccess; +@importtypeexpr = @importtypeaccess | @importnamespaceaccess | @importvartypeaccess; + +@functiontypeexpr = @plainfunctiontypeexpr | @constructortypeexpr; + +// types +types ( + unique int id: @type, + int kind: int ref, + varchar(900) tostring: string ref +); + +#keyset[parent, idx] +type_child ( + int child: @type ref, + int parent: @type ref, + int idx: int ref +); + +case @type.kind of + 0 = @anytype +| 1 = @stringtype +| 2 = @numbertype +| 3 = @uniontype +| 4 = @truetype +| 5 = @falsetype +| 6 = @typereference +| 7 = @objecttype +| 8 = @canonicaltypevariabletype +| 9 = @typeoftype +| 10 = @voidtype +| 11 = @undefinedtype +| 12 = @nulltype +| 13 = @nevertype +| 14 = @plainsymboltype +| 15 = @uniquesymboltype +| 16 = @objectkeywordtype +| 17 = @intersectiontype +| 18 = @tupletype +| 19 = @lexicaltypevariabletype +| 20 = @thistype +| 21 = @numberliteraltype +| 22 = @stringliteraltype +| 23 = @unknowntype +| 24 = @biginttype +| 25 = @bigintliteraltype +; + +@booleanliteraltype = @truetype | @falsetype; +@symboltype = @plainsymboltype | @uniquesymboltype; +@unionorintersectiontype = @uniontype | @intersectiontype; +@typevariabletype = @canonicaltypevariabletype | @lexicaltypevariabletype; + +hasAssertsKeyword(int node: @predicatetypeexpr ref); + +@typed_ast_node = @expr | @typeexpr | @function; +ast_node_type( + unique int node: @typed_ast_node ref, + int typ: @type ref); + +declared_function_signature( + unique int node: @function ref, + int sig: @signature_type ref +); + +invoke_expr_signature( + unique int node: @invokeexpr ref, + int sig: @signature_type ref +); + +invoke_expr_overload_index( + unique int node: @invokeexpr ref, + int index: int ref +); + +symbols ( + unique int id: @symbol, + int kind: int ref, + varchar(900) name: string ref +); + +symbol_parent ( + unique int symbol: @symbol ref, + int parent: @symbol ref +); + +symbol_module ( + int symbol: @symbol ref, + varchar(900) moduleName: string ref +); + +symbol_global ( + int symbol: @symbol ref, + varchar(900) globalName: string ref +); + +case @symbol.kind of + 0 = @root_symbol +| 1 = @member_symbol +| 2 = @other_symbol +; + +@type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype; +@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr; + +ast_node_symbol( + unique int node: @ast_node_with_symbol ref, + int symbol: @symbol ref); + +type_symbol( + unique int typ: @type_with_symbol ref, + int symbol: @symbol ref); + +#keyset[typ, name] +type_property( + int typ: @type ref, + varchar(900) name: string ref, + int propertyType: @type ref); + +type_alias( + unique int aliasType: @type ref, + int underlyingType: @type ref); + +@literaltype = @stringliteraltype | @numberliteraltype | @booleanliteraltype | @bigintliteraltype; +@type_with_literal_value = @stringliteraltype | @numberliteraltype | @bigintliteraltype; +type_literal_value( + unique int typ: @type_with_literal_value ref, + varchar(900) value: string ref); + +signature_types ( + unique int id: @signature_type, + int kind: int ref, + varchar(900) tostring: string ref, + int type_parameters: int ref, + int required_params: int ref +); + +signature_rest_parameter( + unique int sig: @signature_type ref, + int rest_param_arra_type: @type ref +); + +case @signature_type.kind of + 0 = @function_signature_type +| 1 = @constructor_signature_type +; + +#keyset[typ, kind, index] +type_contains_signature ( + int typ: @type ref, + int kind: int ref, // constructor/call/index + int index: int ref, // ordering of overloaded signatures + int sig: @signature_type ref +); + +#keyset[parent, index] +signature_contains_type ( + int child: @type ref, + int parent: @signature_type ref, + int index: int ref +); + +#keyset[sig, index] +signature_parameter_name ( + int sig: @signature_type ref, + int index: int ref, + varchar(900) name: string ref +); + +number_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +string_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +base_type_names( + int typeName: @symbol ref, + int baseTypeName: @symbol ref +); + +self_types( + int typeName: @symbol ref, + int selfType: @typereference ref +); + +tuple_type_min_length( + unique int typ: @type ref, + int minLength: int ref +); + +tuple_type_rest( + unique int typ: @type ref +); + +// comments +comments (unique int id: @comment, + int kind: int ref, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(900) tostring: string ref); + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment +| 2 = @doccomment +| 3 = @htmlcommentstart +| 4 = @htmlcommentend; + +@htmlcomment = @htmlcommentstart | @htmlcommentend; +@linecomment = @slashslashcomment | @htmlcomment; +@blockcomment = @slashstarcomment | @doccomment; + +// source lines +lines (unique int id: @line, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(2) terminator: string ref); +indentation (int file: @file ref, + int lineno: int ref, + varchar(1) indentChar: string ref, + int indentDepth: int ref); + +// JavaScript parse errors +jsParseErrors (unique int id: @js_parse_error, + int toplevel: @toplevel ref, + varchar(900) message: string ref, + varchar(900) line: string ref); + +// regular expressions +#keyset[parent, idx] +regexpterm (unique int id: @regexpterm, + int kind: int ref, + int parent: @regexpparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +@regexpparent = @regexpterm | @regexpliteral | @stringliteral; + +case @regexpterm.kind of + 0 = @regexp_alt +| 1 = @regexp_seq +| 2 = @regexp_caret +| 3 = @regexp_dollar +| 4 = @regexp_wordboundary +| 5 = @regexp_nonwordboundary +| 6 = @regexp_positive_lookahead +| 7 = @regexp_negative_lookahead +| 8 = @regexp_star +| 9 = @regexp_plus +| 10 = @regexp_opt +| 11 = @regexp_range +| 12 = @regexp_dot +| 13 = @regexp_group +| 14 = @regexp_normal_constant +| 15 = @regexp_hex_escape +| 16 = @regexp_unicode_escape +| 17 = @regexp_dec_escape +| 18 = @regexp_oct_escape +| 19 = @regexp_ctrl_escape +| 20 = @regexp_char_class_escape +| 21 = @regexp_id_escape +| 22 = @regexp_backref +| 23 = @regexp_char_class +| 24 = @regexp_char_range +| 25 = @regexp_positive_lookbehind +| 26 = @regexp_negative_lookbehind +| 27 = @regexp_unicode_property_escape; + +regexpParseErrors (unique int id: @regexp_parse_error, + int regexp: @regexpterm ref, + varchar(900) message: string ref); + +@regexp_quantifier = @regexp_star | @regexp_plus | @regexp_opt | @regexp_range; +@regexp_escape = @regexp_char_escape | @regexp_char_class_escape | @regexp_unicode_property_escape; +@regexp_char_escape = @regexp_hex_escape | @regexp_unicode_escape | @regexp_dec_escape | @regexp_oct_escape | @regexp_ctrl_escape | @regexp_id_escape; +@regexp_constant = @regexp_normal_constant | @regexp_char_escape; +@regexp_lookahead = @regexp_positive_lookahead | @regexp_negative_lookahead; +@regexp_lookbehind = @regexp_positive_lookbehind | @regexp_negative_lookbehind; +@regexp_subpattern = @regexp_lookahead | @regexp_lookbehind; +@regexp_anchor = @regexp_dollar | @regexp_caret; + +isGreedy (int id: @regexp_quantifier ref); +rangeQuantifierLowerBound (unique int id: @regexp_range ref, int lo: int ref); +rangeQuantifierUpperBound (unique int id: @regexp_range ref, int hi: int ref); +isCapture (unique int id: @regexp_group ref, int number: int ref); +isNamedCapture (unique int id: @regexp_group ref, string name: string ref); +isInverted (int id: @regexp_char_class ref); +regexpConstValue (unique int id: @regexp_constant ref, varchar(1) value: string ref); +charClassEscape (unique int id: @regexp_char_class_escape ref, varchar(1) value: string ref); +backref (unique int id: @regexp_backref ref, int value: int ref); +namedBackref (unique int id: @regexp_backref ref, string name: string ref); +unicodePropertyEscapeName (unique int id: @regexp_unicode_property_escape ref, string name: string ref); +unicodePropertyEscapeValue (unique int id: @regexp_unicode_property_escape ref, string value: string ref); + +// tokens +#keyset[toplevel, idx] +tokeninfo (unique int id: @token, + int kind: int ref, + int toplevel: @toplevel ref, + int idx: int ref, + varchar(900) value: string ref); + +case @token.kind of + 0 = @token_eof +| 1 = @token_null_literal +| 2 = @token_boolean_literal +| 3 = @token_numeric_literal +| 4 = @token_string_literal +| 5 = @token_regular_expression +| 6 = @token_identifier +| 7 = @token_keyword +| 8 = @token_punctuator; + +// associate comments with the token immediately following them (which may be EOF) +next_token (int comment: @comment ref, int token: @token ref); + +// JSON +#keyset[parent, idx] +json (unique int id: @json_value, + int kind: int ref, + int parent: @json_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +json_literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @json_value ref); + +json_properties (int obj: @json_object ref, + varchar(900) property: string ref, + int value: @json_value ref); + +json_errors (unique int id: @json_parse_error, + varchar(900) message: string ref); + +json_locations(unique int locatable: @json_locatable ref, + int location: @location_default ref); + +case @json_value.kind of + 0 = @json_null +| 1 = @json_boolean +| 2 = @json_number +| 3 = @json_string +| 4 = @json_array +| 5 = @json_object; + +@json_parent = @json_object | @json_array | @file; + +@json_locatable = @json_value | @json_parse_error; + +// locations +@ast_node = @toplevel | @stmt | @expr | @property | @typeexpr; + +@locatable = @file + | @ast_node + | @comment + | @line + | @js_parse_error | @regexp_parse_error + | @regexpterm + | @json_locatable + | @token + | @cfg_node + | @jsdoc | @jsdoc_type_expr | @jsdoc_tag + | @yaml_locatable + | @xmllocatable + | @configLocatable; + +hasLocation (unique int locatable: @locatable ref, + int location: @location ref); + +// CFG +entry_cfg_node (unique int id: @entry_node, int container: @stmt_container ref); +exit_cfg_node (unique int id: @exit_node, int container: @stmt_container ref); +guard_node (unique int id: @guard_node, int kind: int ref, int test: @expr ref); +case @guard_node.kind of + 0 = @falsy_guard +| 1 = @truthy_guard; +@condition_guard = @falsy_guard | @truthy_guard; + +@synthetic_cfg_node = @entry_node | @exit_node | @guard_node; +@cfg_node = @synthetic_cfg_node | @exprparent; + +successor (int pred: @cfg_node ref, int succ: @cfg_node ref); + +// JSDoc comments +jsdoc (unique int id: @jsdoc, varchar(900) description: string ref, int comment: @comment ref); +#keyset[parent, idx] +jsdoc_tags (unique int id: @jsdoc_tag, varchar(900) title: string ref, + int parent: @jsdoc ref, int idx: int ref, varchar(900) tostring: string ref); +jsdoc_tag_descriptions (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); +jsdoc_tag_names (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); + +#keyset[parent, idx] +jsdoc_type_exprs (unique int id: @jsdoc_type_expr, + int kind: int ref, + int parent: @jsdoc_type_expr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); +case @jsdoc_type_expr.kind of + 0 = @jsdoc_any_type_expr +| 1 = @jsdoc_null_type_expr +| 2 = @jsdoc_undefined_type_expr +| 3 = @jsdoc_unknown_type_expr +| 4 = @jsdoc_void_type_expr +| 5 = @jsdoc_named_type_expr +| 6 = @jsdoc_applied_type_expr +| 7 = @jsdoc_nullable_type_expr +| 8 = @jsdoc_non_nullable_type_expr +| 9 = @jsdoc_record_type_expr +| 10 = @jsdoc_array_type_expr +| 11 = @jsdoc_union_type_expr +| 12 = @jsdoc_function_type_expr +| 13 = @jsdoc_optional_type_expr +| 14 = @jsdoc_rest_type_expr +; + +#keyset[id, idx] +jsdoc_record_field_name (int id: @jsdoc_record_type_expr ref, int idx: int ref, varchar(900) name: string ref); +jsdoc_prefix_qualifier (int id: @jsdoc_type_expr ref); +jsdoc_has_new_parameter (int fn: @jsdoc_function_type_expr ref); + +@jsdoc_type_expr_parent = @jsdoc_type_expr | @jsdoc_tag; + +jsdoc_errors (unique int id: @jsdoc_error, int tag: @jsdoc_tag ref, varchar(900) message: string ref, varchar(900) tostring: string ref); + +// YAML +#keyset[parent, idx] +yaml (unique int id: @yaml_node, + int kind: int ref, + int parent: @yaml_node_parent ref, + int idx: int ref, + varchar(900) tag: string ref, + varchar(900) tostring: string ref); + +case @yaml_node.kind of + 0 = @yaml_scalar_node +| 1 = @yaml_mapping_node +| 2 = @yaml_sequence_node +| 3 = @yaml_alias_node +; + +@yaml_collection_node = @yaml_mapping_node | @yaml_sequence_node; + +@yaml_node_parent = @yaml_collection_node | @file; + +yaml_anchors (unique int node: @yaml_node ref, + varchar(900) anchor: string ref); + +yaml_aliases (unique int alias: @yaml_alias_node ref, + varchar(900) target: string ref); + +yaml_scalars (unique int scalar: @yaml_scalar_node ref, + int style: int ref, + varchar(900) value: string ref); + +yaml_errors (unique int id: @yaml_error, + varchar(900) message: string ref); + +yaml_locations(unique int locatable: @yaml_locatable ref, + int location: @location_default ref); + +@yaml_locatable = @yaml_node | @yaml_error; + +/* XML Files */ + +xmlEncoding( + unique int id: @file ref, + varchar(900) encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + varchar(900) root: string ref, + varchar(900) publicId: string ref, + varchar(900) systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + varchar(900) name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + varchar(900) name: string ref, + varchar(3600) value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + varchar(900) prefixName: string ref, + varchar(900) URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +@dataflownode = @expr | @functiondeclstmt | @classdeclstmt | @namespacedeclaration | @enumdeclaration | @property; + +@optionalchainable = @callexpr | @propaccess; + +isOptionalChaining(int id: @optionalchainable ref); + +/* + * configuration files with key value pairs + */ + +configs( + unique int id: @config +); + +configNames( + unique int id: @configName, + int config: @config ref, + string name: string ref +); + +configValues( + unique int id: @configValue, + int config: @config ref, + string value: string ref +); + +configLocations( + int locatable: @configLocatable ref, + int location: @location_default ref +); + +@configLocatable = @config | @configName | @configValue; + +/** + * The time taken for the extraction of a file. + * This table contains non-deterministic content. + * + * The sum of the `time` column for each (`file`, `timerKind`) pair + * is the total time taken for extraction of `file`. The `extractionPhase` + * column provides a granular view of the extraction time of the file. + */ +extraction_time( + int file : @file ref, + // see `com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase`. + int extractionPhase: int ref, + // 0 for the elapsed CPU time in nanoseconds, 1 for the elapsed wallclock time in nanoseconds + int timerKind: int ref, + float time: float ref +) + +/** + * Non-timing related data for the extraction of a single file. + * This table contains non-deterministic content. + */ +extraction_data( + int file : @file ref, + // the absolute path to the cache file + varchar(900) cacheFile: string ref, + boolean fromCache: boolean ref, + int length: int ref +) diff --git a/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/semmlecode.javascript.dbscheme b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/semmlecode.javascript.dbscheme new file mode 100644 index 00000000000..dad09eeeff5 --- /dev/null +++ b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/semmlecode.javascript.dbscheme @@ -0,0 +1,1186 @@ +/*** Standard fragments ***/ + +/** Files and folders **/ + +@location = @location_default; + +locations_default(unique int id: @location_default, + int file: @file ref, + int beginLine: int ref, + int beginColumn: int ref, + int endLine: int ref, + int endColumn: int ref + ); + +@sourceline = @locatable; + +numlines(int element_id: @sourceline ref, + int num_lines: int ref, + int num_code: int ref, + int num_comment: int ref + ); + + +/* + fromSource(0) = unknown, + fromSource(1) = from source, + fromSource(2) = from library +*/ +files(unique int id: @file, + varchar(900) name: string ref, + varchar(900) simple: string ref, + varchar(900) ext: string ref, + int fromSource: int ref); + +folders(unique int id: @folder, + varchar(900) name: string ref, + varchar(900) simple: string ref); + + +@container = @folder | @file ; + + +containerparent(int parent: @container ref, + unique int child: @container ref); + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +/** Version control data **/ + +svnentries( + int id : @svnentry, + varchar(500) revision : string ref, + varchar(500) author : string ref, + date revisionDate : date ref, + int changeSize : int ref +); + +svnaffectedfiles( + int id : @svnentry ref, + int file : @file ref, + varchar(500) action : string ref +); + +svnentrymsg( + int id : @svnentry ref, + varchar(500) message : string ref +); + +svnchurn( + int commit : @svnentry ref, + int file : @file ref, + int addedLines : int ref, + int deletedLines : int ref +); + + +/*** JavaScript-specific part ***/ + +filetype( + int file: @file ref, + string filetype: string ref +) + +// top-level code fragments +toplevels (unique int id: @toplevel, + int kind: int ref); + +isExterns (int toplevel: @toplevel ref); + +case @toplevel.kind of + 0 = @script +| 1 = @inline_script +| 2 = @event_handler +| 3 = @javascript_url; + +isModule (int tl: @toplevel ref); +isNodejs (int tl: @toplevel ref); +isES2015Module (int tl: @toplevel ref); +isClosureModule (int tl: @toplevel ref); + +// statements +#keyset[parent, idx] +stmts (unique int id: @stmt, + int kind: int ref, + int parent: @stmtparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +stmtContainers (unique int stmt: @stmt ref, + int container: @stmt_container ref); + +jumpTargets (unique int jump: @stmt ref, + int target: @stmt ref); + +@stmtparent = @stmt | @toplevel | @functionexpr | @arrowfunctionexpr; +@stmt_container = @toplevel | @function | @namespacedeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration; + +case @stmt.kind of + 0 = @emptystmt +| 1 = @blockstmt +| 2 = @exprstmt +| 3 = @ifstmt +| 4 = @labeledstmt +| 5 = @breakstmt +| 6 = @continuestmt +| 7 = @withstmt +| 8 = @switchstmt +| 9 = @returnstmt +| 10 = @throwstmt +| 11 = @trystmt +| 12 = @whilestmt +| 13 = @dowhilestmt +| 14 = @forstmt +| 15 = @forinstmt +| 16 = @debuggerstmt +| 17 = @functiondeclstmt +| 18 = @vardeclstmt +| 19 = @case +| 20 = @catchclause +| 21 = @forofstmt +| 22 = @constdeclstmt +| 23 = @letstmt +| 24 = @legacy_letstmt +| 25 = @foreachstmt +| 26 = @classdeclstmt +| 27 = @importdeclaration +| 28 = @exportalldeclaration +| 29 = @exportdefaultdeclaration +| 30 = @exportnameddeclaration +| 31 = @namespacedeclaration +| 32 = @importequalsdeclaration +| 33 = @exportassigndeclaration +| 34 = @interfacedeclaration +| 35 = @typealiasdeclaration +| 36 = @enumdeclaration +| 37 = @externalmoduledeclaration +| 38 = @exportasnamespacedeclaration +| 39 = @globalaugmentationdeclaration +; + +@declstmt = @vardeclstmt | @constdeclstmt | @letstmt | @legacy_letstmt; + +@exportdeclaration = @exportalldeclaration | @exportdefaultdeclaration | @exportnameddeclaration; + +@namespacedefinition = @namespacedeclaration | @enumdeclaration; +@typedefinition = @classdefinition | @interfacedeclaration | @enumdeclaration | @typealiasdeclaration | @enum_member; + +isInstantiated(unique int decl: @namespacedeclaration ref); + +@declarablenode = @declstmt | @namespacedeclaration | @classdeclstmt | @functiondeclstmt | @enumdeclaration | @externalmoduledeclaration | @globalaugmentationdeclaration | @field; +hasDeclareKeyword(unique int stmt: @declarablenode ref); + +isForAwaitOf(unique int forof: @forofstmt ref); + +// expressions +#keyset[parent, idx] +exprs (unique int id: @expr, + int kind: int ref, + int parent: @exprparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @exprortype ref); + +enclosingStmt (unique int expr: @exprortype ref, + int stmt: @stmt ref); + +exprContainers (unique int expr: @exprortype ref, + int container: @stmt_container ref); + +arraySize (unique int ae: @arraylike ref, + int sz: int ref); + +isDelegating (int yield: @yieldexpr ref); + +@exprorstmt = @expr | @stmt; +@exprortype = @expr | @typeexpr; +@exprparent = @exprorstmt | @property | @functiontypeexpr; +@arraylike = @arrayexpr | @arraypattern; +@type_annotation = @typeexpr | @jsdoc_type_expr; + +case @expr.kind of + 0 = @label +| 1 = @nullliteral +| 2 = @booleanliteral +| 3 = @numberliteral +| 4 = @stringliteral +| 5 = @regexpliteral +| 6 = @thisexpr +| 7 = @arrayexpr +| 8 = @objexpr +| 9 = @functionexpr +| 10 = @seqexpr +| 11 = @conditionalexpr +| 12 = @newexpr +| 13 = @callexpr +| 14 = @dotexpr +| 15 = @indexexpr +| 16 = @negexpr +| 17 = @plusexpr +| 18 = @lognotexpr +| 19 = @bitnotexpr +| 20 = @typeofexpr +| 21 = @voidexpr +| 22 = @deleteexpr +| 23 = @eqexpr +| 24 = @neqexpr +| 25 = @eqqexpr +| 26 = @neqqexpr +| 27 = @ltexpr +| 28 = @leexpr +| 29 = @gtexpr +| 30 = @geexpr +| 31 = @lshiftexpr +| 32 = @rshiftexpr +| 33 = @urshiftexpr +| 34 = @addexpr +| 35 = @subexpr +| 36 = @mulexpr +| 37 = @divexpr +| 38 = @modexpr +| 39 = @bitorexpr +| 40 = @xorexpr +| 41 = @bitandexpr +| 42 = @inexpr +| 43 = @instanceofexpr +| 44 = @logandexpr +| 45 = @logorexpr +| 47 = @assignexpr +| 48 = @assignaddexpr +| 49 = @assignsubexpr +| 50 = @assignmulexpr +| 51 = @assigndivexpr +| 52 = @assignmodexpr +| 53 = @assignlshiftexpr +| 54 = @assignrshiftexpr +| 55 = @assignurshiftexpr +| 56 = @assignorexpr +| 57 = @assignxorexpr +| 58 = @assignandexpr +| 59 = @preincexpr +| 60 = @postincexpr +| 61 = @predecexpr +| 62 = @postdecexpr +| 63 = @parexpr +| 64 = @vardeclarator +| 65 = @arrowfunctionexpr +| 66 = @spreadelement +| 67 = @arraypattern +| 68 = @objectpattern +| 69 = @yieldexpr +| 70 = @taggedtemplateexpr +| 71 = @templateliteral +| 72 = @templateelement +| 73 = @arraycomprehensionexpr +| 74 = @generatorexpr +| 75 = @forincomprehensionblock +| 76 = @forofcomprehensionblock +| 77 = @legacy_letexpr +| 78 = @vardecl +| 79 = @proper_varaccess +| 80 = @classexpr +| 81 = @superexpr +| 82 = @newtargetexpr +| 83 = @namedimportspecifier +| 84 = @importdefaultspecifier +| 85 = @importnamespacespecifier +| 86 = @namedexportspecifier +| 87 = @expexpr +| 88 = @assignexpexpr +| 89 = @jsxelement +| 90 = @jsxqualifiedname +| 91 = @jsxemptyexpr +| 92 = @awaitexpr +| 93 = @functionsentexpr +| 94 = @decorator +| 95 = @exportdefaultspecifier +| 96 = @exportnamespacespecifier +| 97 = @bindexpr +| 98 = @externalmodulereference +| 99 = @dynamicimport +| 100 = @expressionwithtypearguments +| 101 = @prefixtypeassertion +| 102 = @astypeassertion +| 103 = @export_varaccess +| 104 = @decorator_list +| 105 = @non_null_assertion +| 106 = @bigintliteral +| 107 = @nullishcoalescingexpr +| 108 = @e4x_xml_anyname +| 109 = @e4x_xml_static_attribute_selector +| 110 = @e4x_xml_dynamic_attribute_selector +| 111 = @e4x_xml_filter_expression +| 112 = @e4x_xml_static_qualident +| 113 = @e4x_xml_dynamic_qualident +| 114 = @e4x_xml_dotdotexpr +| 115 = @importmetaexpr +; + +@varaccess = @proper_varaccess | @export_varaccess; +@varref = @vardecl | @varaccess; + +@identifier = @label | @varref | @typeidentifier; + +@literal = @nullliteral | @booleanliteral | @numberliteral | @stringliteral | @regexpliteral | @bigintliteral; + +@propaccess = @dotexpr | @indexexpr; + +@invokeexpr = @newexpr | @callexpr; + +@unaryexpr = @negexpr | @plusexpr | @lognotexpr | @bitnotexpr | @typeofexpr | @voidexpr | @deleteexpr | @spreadelement; + +@equalitytest = @eqexpr | @neqexpr | @eqqexpr | @neqqexpr; + +@comparison = @equalitytest | @ltexpr | @leexpr | @gtexpr | @geexpr; + +@binaryexpr = @comparison | @lshiftexpr | @rshiftexpr | @urshiftexpr | @addexpr | @subexpr | @mulexpr | @divexpr | @modexpr | @expexpr | @bitorexpr | @xorexpr | @bitandexpr | @inexpr | @instanceofexpr | @logandexpr | @logorexpr | @nullishcoalescingexpr; + +@assignment = @assignexpr | @assignaddexpr | @assignsubexpr | @assignmulexpr | @assigndivexpr | @assignmodexpr | @assignexpexpr | @assignlshiftexpr | @assignrshiftexpr | @assignurshiftexpr | @assignorexpr | @assignxorexpr | @assignandexpr; + +@updateexpr = @preincexpr | @postincexpr | @predecexpr | @postdecexpr; + +@pattern = @varref | @arraypattern | @objectpattern; + +@comprehensionexpr = @arraycomprehensionexpr | @generatorexpr; + +@comprehensionblock = @forincomprehensionblock | @forofcomprehensionblock; + +@importspecifier = @namedimportspecifier | @importdefaultspecifier | @importnamespacespecifier; + +@exportspecifier = @namedexportspecifier | @exportdefaultspecifier | @exportnamespacespecifier; + +@typeassertion = @astypeassertion | @prefixtypeassertion; + +@classdefinition = @classdeclstmt | @classexpr; +@interfacedefinition = @interfacedeclaration | @interfacetypeexpr; +@classorinterface = @classdefinition | @interfacedefinition; + +@lexical_decl = @vardecl | @typedecl; +@lexical_access = @varaccess | @localtypeaccess | @localvartypeaccess | @localnamespaceaccess; +@lexical_ref = @lexical_decl | @lexical_access; + +@e4x_xml_attribute_selector = @e4x_xml_static_attribute_selector | @e4x_xml_dynamic_attribute_selector; +@e4x_xml_qualident = @e4x_xml_static_qualident | @e4x_xml_dynamic_qualident; + +// scopes +scopes (unique int id: @scope, + int kind: int ref); + +case @scope.kind of + 0 = @globalscope +| 1 = @functionscope +| 2 = @catchscope +| 3 = @modulescope +| 4 = @blockscope +| 5 = @forscope +| 6 = @forinscope // for-of scopes work the same as for-in scopes +| 7 = @comprehensionblockscope +| 8 = @classexprscope +| 9 = @namespacescope +| 10 = @classdeclscope +| 11 = @interfacescope +| 12 = @typealiasscope +| 13 = @mappedtypescope +| 14 = @enumscope +| 15 = @externalmodulescope +| 16 = @conditionaltypescope; + +scopenodes (unique int node: @ast_node ref, + int scope: @scope ref); + +scopenesting (unique int inner: @scope ref, + int outer: @scope ref); + +// functions +@function = @functiondeclstmt | @functionexpr | @arrowfunctionexpr; + +@parameterized = @function | @catchclause; +@type_parameterized = @function | @classorinterface | @typealiasdeclaration | @mappedtypeexpr | @infertypeexpr; + +isGenerator (int fun: @function ref); +hasRestParameter (int fun: @function ref); +isAsync (int fun: @function ref); + +// variables and lexically scoped type names +#keyset[scope, name] +variables (unique int id: @variable, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_type_names (unique int id: @local_type_name, + varchar(900) name: string ref, + int scope: @scope ref); + +#keyset[scope, name] +local_namespace_names (unique int id: @local_namespace_name, + varchar(900) name: string ref, + int scope: @scope ref); + +isArgumentsObject (int id: @variable ref); + +@lexical_name = @variable | @local_type_name | @local_namespace_name; + +@bind_id = @varaccess | @localvartypeaccess; +bind (unique int id: @bind_id ref, + int decl: @variable ref); + +decl (unique int id: @vardecl ref, + int decl: @variable ref); + +@typebind_id = @localtypeaccess | @export_varaccess; +typebind (unique int id: @typebind_id ref, + int decl: @local_type_name ref); + +@typedecl_id = @typedecl | @vardecl; +typedecl (unique int id: @typedecl_id ref, + int decl: @local_type_name ref); + +namespacedecl (unique int id: @vardecl ref, + int decl: @local_namespace_name ref); + +@namespacebind_id = @localnamespaceaccess | @export_varaccess; +namespacebind (unique int id: @namespacebind_id ref, + int decl: @local_namespace_name ref); + + +// properties in object literals, property patterns in object patterns, and method declarations in classes +#keyset[parent, index] +properties (unique int id: @property, + int parent: @property_parent ref, + int index: int ref, + int kind: int ref, + varchar(900) tostring: string ref); + +case @property.kind of + 0 = @value_property +| 1 = @property_getter +| 2 = @property_setter +| 3 = @jsx_attribute +| 4 = @function_call_signature +| 5 = @constructor_call_signature +| 6 = @index_signature +| 7 = @enum_member +| 8 = @proper_field +| 9 = @parameter_field +; + +@property_parent = @objexpr | @objectpattern | @classdefinition | @jsxelement | @interfacedefinition | @enumdeclaration; +@property_accessor = @property_getter | @property_setter; +@call_signature = @function_call_signature | @constructor_call_signature; +@field = @proper_field | @parameter_field; +@field_or_vardeclarator = @field | @vardeclarator; + +isComputed (int id: @property ref); +isMethod (int id: @property ref); +isStatic (int id: @property ref); +isAbstractMember (int id: @property ref); +isConstEnum (int id: @enumdeclaration ref); +isAbstractClass (int id: @classdeclstmt ref); + +hasPublicKeyword (int id: @property ref); +hasPrivateKeyword (int id: @property ref); +hasProtectedKeyword (int id: @property ref); +hasReadonlyKeyword (int id: @property ref); +isOptionalMember (int id: @property ref); +hasDefiniteAssignmentAssertion (int id: @field_or_vardeclarator ref); +isOptionalParameterDeclaration (unique int parameter: @pattern ref); + +#keyset[constructor, param_index] +parameter_fields( + unique int field: @parameter_field ref, + int constructor: @functionexpr ref, + int param_index: int ref +); + +// types +#keyset[parent, idx] +typeexprs ( + unique int id: @typeexpr, + int kind: int ref, + int parent: @typeexpr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref +); + +case @typeexpr.kind of + 0 = @localtypeaccess +| 1 = @typedecl +| 2 = @keywordtypeexpr +| 3 = @stringliteraltypeexpr +| 4 = @numberliteraltypeexpr +| 5 = @booleanliteraltypeexpr +| 6 = @arraytypeexpr +| 7 = @uniontypeexpr +| 8 = @indexedaccesstypeexpr +| 9 = @intersectiontypeexpr +| 10 = @parenthesizedtypeexpr +| 11 = @tupletypeexpr +| 12 = @keyoftypeexpr +| 13 = @qualifiedtypeaccess +| 14 = @generictypeexpr +| 15 = @typelabel +| 16 = @typeoftypeexpr +| 17 = @localvartypeaccess +| 18 = @qualifiedvartypeaccess +| 19 = @thisvartypeaccess +| 20 = @predicatetypeexpr +| 21 = @interfacetypeexpr +| 22 = @typeparameter +| 23 = @plainfunctiontypeexpr +| 24 = @constructortypeexpr +| 25 = @localnamespaceaccess +| 26 = @qualifiednamespaceaccess +| 27 = @mappedtypeexpr +| 28 = @conditionaltypeexpr +| 29 = @infertypeexpr +| 30 = @importtypeaccess +| 31 = @importnamespaceaccess +| 32 = @importvartypeaccess +| 33 = @optionaltypeexpr +| 34 = @resttypeexpr +| 35 = @bigintliteraltypeexpr +| 36 = @readonlytypeexpr +; + +@typeref = @typeaccess | @typedecl; +@typeidentifier = @typedecl | @localtypeaccess | @typelabel | @localvartypeaccess | @localnamespaceaccess; +@typeexpr_parent = @expr | @stmt | @property | @typeexpr; +@literaltypeexpr = @stringliteraltypeexpr | @numberliteraltypeexpr | @booleanliteraltypeexpr | @bigintliteraltypeexpr; +@typeaccess = @localtypeaccess | @qualifiedtypeaccess | @importtypeaccess; +@vartypeaccess = @localvartypeaccess | @qualifiedvartypeaccess | @thisvartypeaccess | @importvartypeaccess; +@namespaceaccess = @localnamespaceaccess | @qualifiednamespaceaccess | @importnamespaceaccess; +@importtypeexpr = @importtypeaccess | @importnamespaceaccess | @importvartypeaccess; + +@functiontypeexpr = @plainfunctiontypeexpr | @constructortypeexpr; + +// types +types ( + unique int id: @type, + int kind: int ref, + varchar(900) tostring: string ref +); + +#keyset[parent, idx] +type_child ( + int child: @type ref, + int parent: @type ref, + int idx: int ref +); + +case @type.kind of + 0 = @anytype +| 1 = @stringtype +| 2 = @numbertype +| 3 = @uniontype +| 4 = @truetype +| 5 = @falsetype +| 6 = @typereference +| 7 = @objecttype +| 8 = @canonicaltypevariabletype +| 9 = @typeoftype +| 10 = @voidtype +| 11 = @undefinedtype +| 12 = @nulltype +| 13 = @nevertype +| 14 = @plainsymboltype +| 15 = @uniquesymboltype +| 16 = @objectkeywordtype +| 17 = @intersectiontype +| 18 = @tupletype +| 19 = @lexicaltypevariabletype +| 20 = @thistype +| 21 = @numberliteraltype +| 22 = @stringliteraltype +| 23 = @unknowntype +| 24 = @biginttype +| 25 = @bigintliteraltype +; + +@booleanliteraltype = @truetype | @falsetype; +@symboltype = @plainsymboltype | @uniquesymboltype; +@unionorintersectiontype = @uniontype | @intersectiontype; +@typevariabletype = @canonicaltypevariabletype | @lexicaltypevariabletype; + +hasAssertsKeyword(int node: @predicatetypeexpr ref); + +@typed_ast_node = @expr | @typeexpr | @function; +ast_node_type( + unique int node: @typed_ast_node ref, + int typ: @type ref); + +declared_function_signature( + unique int node: @function ref, + int sig: @signature_type ref +); + +invoke_expr_signature( + unique int node: @invokeexpr ref, + int sig: @signature_type ref +); + +invoke_expr_overload_index( + unique int node: @invokeexpr ref, + int index: int ref +); + +symbols ( + unique int id: @symbol, + int kind: int ref, + varchar(900) name: string ref +); + +symbol_parent ( + unique int symbol: @symbol ref, + int parent: @symbol ref +); + +symbol_module ( + int symbol: @symbol ref, + varchar(900) moduleName: string ref +); + +symbol_global ( + int symbol: @symbol ref, + varchar(900) globalName: string ref +); + +case @symbol.kind of + 0 = @root_symbol +| 1 = @member_symbol +| 2 = @other_symbol +; + +@type_with_symbol = @typereference | @typevariabletype | @typeoftype | @uniquesymboltype; +@ast_node_with_symbol = @typedefinition | @namespacedefinition | @toplevel | @typeaccess | @namespaceaccess | @vardecl | @function | @invokeexpr | @importdeclaration | @externalmodulereference; + +ast_node_symbol( + unique int node: @ast_node_with_symbol ref, + int symbol: @symbol ref); + +type_symbol( + unique int typ: @type_with_symbol ref, + int symbol: @symbol ref); + +#keyset[typ, name] +type_property( + int typ: @type ref, + varchar(900) name: string ref, + int propertyType: @type ref); + +type_alias( + unique int aliasType: @type ref, + int underlyingType: @type ref); + +@literaltype = @stringliteraltype | @numberliteraltype | @booleanliteraltype | @bigintliteraltype; +@type_with_literal_value = @stringliteraltype | @numberliteraltype | @bigintliteraltype; +type_literal_value( + unique int typ: @type_with_literal_value ref, + varchar(900) value: string ref); + +signature_types ( + unique int id: @signature_type, + int kind: int ref, + varchar(900) tostring: string ref, + int type_parameters: int ref, + int required_params: int ref +); + +signature_rest_parameter( + unique int sig: @signature_type ref, + int rest_param_arra_type: @type ref +); + +case @signature_type.kind of + 0 = @function_signature_type +| 1 = @constructor_signature_type +; + +#keyset[typ, kind, index] +type_contains_signature ( + int typ: @type ref, + int kind: int ref, // constructor/call/index + int index: int ref, // ordering of overloaded signatures + int sig: @signature_type ref +); + +#keyset[parent, index] +signature_contains_type ( + int child: @type ref, + int parent: @signature_type ref, + int index: int ref +); + +#keyset[sig, index] +signature_parameter_name ( + int sig: @signature_type ref, + int index: int ref, + varchar(900) name: string ref +); + +number_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +string_index_type ( + unique int baseType: @type ref, + int propertyType: @type ref +); + +base_type_names( + int typeName: @symbol ref, + int baseTypeName: @symbol ref +); + +self_types( + int typeName: @symbol ref, + int selfType: @typereference ref +); + +tuple_type_min_length( + unique int typ: @type ref, + int minLength: int ref +); + +tuple_type_rest( + unique int typ: @type ref +); + +// comments +comments (unique int id: @comment, + int kind: int ref, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(900) tostring: string ref); + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment +| 2 = @doccomment +| 3 = @htmlcommentstart +| 4 = @htmlcommentend; + +@htmlcomment = @htmlcommentstart | @htmlcommentend; +@linecomment = @slashslashcomment | @htmlcomment; +@blockcomment = @slashstarcomment | @doccomment; + +// source lines +lines (unique int id: @line, + int toplevel: @toplevel ref, + varchar(900) text: string ref, + varchar(2) terminator: string ref); +indentation (int file: @file ref, + int lineno: int ref, + varchar(1) indentChar: string ref, + int indentDepth: int ref); + +// JavaScript parse errors +jsParseErrors (unique int id: @js_parse_error, + int toplevel: @toplevel ref, + varchar(900) message: string ref, + varchar(900) line: string ref); + +// regular expressions +#keyset[parent, idx] +regexpterm (unique int id: @regexpterm, + int kind: int ref, + int parent: @regexpparent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +@regexpparent = @regexpterm | @regexpliteral | @stringliteral; + +case @regexpterm.kind of + 0 = @regexp_alt +| 1 = @regexp_seq +| 2 = @regexp_caret +| 3 = @regexp_dollar +| 4 = @regexp_wordboundary +| 5 = @regexp_nonwordboundary +| 6 = @regexp_positive_lookahead +| 7 = @regexp_negative_lookahead +| 8 = @regexp_star +| 9 = @regexp_plus +| 10 = @regexp_opt +| 11 = @regexp_range +| 12 = @regexp_dot +| 13 = @regexp_group +| 14 = @regexp_normal_constant +| 15 = @regexp_hex_escape +| 16 = @regexp_unicode_escape +| 17 = @regexp_dec_escape +| 18 = @regexp_oct_escape +| 19 = @regexp_ctrl_escape +| 20 = @regexp_char_class_escape +| 21 = @regexp_id_escape +| 22 = @regexp_backref +| 23 = @regexp_char_class +| 24 = @regexp_char_range +| 25 = @regexp_positive_lookbehind +| 26 = @regexp_negative_lookbehind +| 27 = @regexp_unicode_property_escape; + +regexpParseErrors (unique int id: @regexp_parse_error, + int regexp: @regexpterm ref, + varchar(900) message: string ref); + +@regexp_quantifier = @regexp_star | @regexp_plus | @regexp_opt | @regexp_range; +@regexp_escape = @regexp_char_escape | @regexp_char_class_escape | @regexp_unicode_property_escape; +@regexp_char_escape = @regexp_hex_escape | @regexp_unicode_escape | @regexp_dec_escape | @regexp_oct_escape | @regexp_ctrl_escape | @regexp_id_escape; +@regexp_constant = @regexp_normal_constant | @regexp_char_escape; +@regexp_lookahead = @regexp_positive_lookahead | @regexp_negative_lookahead; +@regexp_lookbehind = @regexp_positive_lookbehind | @regexp_negative_lookbehind; +@regexp_subpattern = @regexp_lookahead | @regexp_lookbehind; +@regexp_anchor = @regexp_dollar | @regexp_caret; + +isGreedy (int id: @regexp_quantifier ref); +rangeQuantifierLowerBound (unique int id: @regexp_range ref, int lo: int ref); +rangeQuantifierUpperBound (unique int id: @regexp_range ref, int hi: int ref); +isCapture (unique int id: @regexp_group ref, int number: int ref); +isNamedCapture (unique int id: @regexp_group ref, string name: string ref); +isInverted (int id: @regexp_char_class ref); +regexpConstValue (unique int id: @regexp_constant ref, varchar(1) value: string ref); +charClassEscape (unique int id: @regexp_char_class_escape ref, varchar(1) value: string ref); +backref (unique int id: @regexp_backref ref, int value: int ref); +namedBackref (unique int id: @regexp_backref ref, string name: string ref); +unicodePropertyEscapeName (unique int id: @regexp_unicode_property_escape ref, string name: string ref); +unicodePropertyEscapeValue (unique int id: @regexp_unicode_property_escape ref, string value: string ref); + +// tokens +#keyset[toplevel, idx] +tokeninfo (unique int id: @token, + int kind: int ref, + int toplevel: @toplevel ref, + int idx: int ref, + varchar(900) value: string ref); + +case @token.kind of + 0 = @token_eof +| 1 = @token_null_literal +| 2 = @token_boolean_literal +| 3 = @token_numeric_literal +| 4 = @token_string_literal +| 5 = @token_regular_expression +| 6 = @token_identifier +| 7 = @token_keyword +| 8 = @token_punctuator; + +// associate comments with the token immediately following them (which may be EOF) +next_token (int comment: @comment ref, int token: @token ref); + +// JSON +#keyset[parent, idx] +json (unique int id: @json_value, + int kind: int ref, + int parent: @json_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); + +json_literals (varchar(900) value: string ref, + varchar(900) raw: string ref, + unique int expr: @json_value ref); + +json_properties (int obj: @json_object ref, + varchar(900) property: string ref, + int value: @json_value ref); + +json_errors (unique int id: @json_parse_error, + varchar(900) message: string ref); + +json_locations(unique int locatable: @json_locatable ref, + int location: @location_default ref); + +case @json_value.kind of + 0 = @json_null +| 1 = @json_boolean +| 2 = @json_number +| 3 = @json_string +| 4 = @json_array +| 5 = @json_object; + +@json_parent = @json_object | @json_array | @file; + +@json_locatable = @json_value | @json_parse_error; + +// locations +@ast_node = @toplevel | @stmt | @expr | @property | @typeexpr; + +@locatable = @file + | @ast_node + | @comment + | @line + | @js_parse_error | @regexp_parse_error + | @regexpterm + | @json_locatable + | @token + | @cfg_node + | @jsdoc | @jsdoc_type_expr | @jsdoc_tag + | @yaml_locatable + | @xmllocatable + | @configLocatable; + +hasLocation (unique int locatable: @locatable ref, + int location: @location ref); + +// CFG +entry_cfg_node (unique int id: @entry_node, int container: @stmt_container ref); +exit_cfg_node (unique int id: @exit_node, int container: @stmt_container ref); +guard_node (unique int id: @guard_node, int kind: int ref, int test: @expr ref); +case @guard_node.kind of + 0 = @falsy_guard +| 1 = @truthy_guard; +@condition_guard = @falsy_guard | @truthy_guard; + +@synthetic_cfg_node = @entry_node | @exit_node | @guard_node; +@cfg_node = @synthetic_cfg_node | @exprparent; + +successor (int pred: @cfg_node ref, int succ: @cfg_node ref); + +// JSDoc comments +jsdoc (unique int id: @jsdoc, varchar(900) description: string ref, int comment: @comment ref); +#keyset[parent, idx] +jsdoc_tags (unique int id: @jsdoc_tag, varchar(900) title: string ref, + int parent: @jsdoc ref, int idx: int ref, varchar(900) tostring: string ref); +jsdoc_tag_descriptions (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); +jsdoc_tag_names (unique int tag: @jsdoc_tag ref, varchar(900) text: string ref); + +#keyset[parent, idx] +jsdoc_type_exprs (unique int id: @jsdoc_type_expr, + int kind: int ref, + int parent: @jsdoc_type_expr_parent ref, + int idx: int ref, + varchar(900) tostring: string ref); +case @jsdoc_type_expr.kind of + 0 = @jsdoc_any_type_expr +| 1 = @jsdoc_null_type_expr +| 2 = @jsdoc_undefined_type_expr +| 3 = @jsdoc_unknown_type_expr +| 4 = @jsdoc_void_type_expr +| 5 = @jsdoc_named_type_expr +| 6 = @jsdoc_applied_type_expr +| 7 = @jsdoc_nullable_type_expr +| 8 = @jsdoc_non_nullable_type_expr +| 9 = @jsdoc_record_type_expr +| 10 = @jsdoc_array_type_expr +| 11 = @jsdoc_union_type_expr +| 12 = @jsdoc_function_type_expr +| 13 = @jsdoc_optional_type_expr +| 14 = @jsdoc_rest_type_expr +; + +#keyset[id, idx] +jsdoc_record_field_name (int id: @jsdoc_record_type_expr ref, int idx: int ref, varchar(900) name: string ref); +jsdoc_prefix_qualifier (int id: @jsdoc_type_expr ref); +jsdoc_has_new_parameter (int fn: @jsdoc_function_type_expr ref); + +@jsdoc_type_expr_parent = @jsdoc_type_expr | @jsdoc_tag; + +jsdoc_errors (unique int id: @jsdoc_error, int tag: @jsdoc_tag ref, varchar(900) message: string ref, varchar(900) tostring: string ref); + +// YAML +#keyset[parent, idx] +yaml (unique int id: @yaml_node, + int kind: int ref, + int parent: @yaml_node_parent ref, + int idx: int ref, + varchar(900) tag: string ref, + varchar(900) tostring: string ref); + +case @yaml_node.kind of + 0 = @yaml_scalar_node +| 1 = @yaml_mapping_node +| 2 = @yaml_sequence_node +| 3 = @yaml_alias_node +; + +@yaml_collection_node = @yaml_mapping_node | @yaml_sequence_node; + +@yaml_node_parent = @yaml_collection_node | @file; + +yaml_anchors (unique int node: @yaml_node ref, + varchar(900) anchor: string ref); + +yaml_aliases (unique int alias: @yaml_alias_node ref, + varchar(900) target: string ref); + +yaml_scalars (unique int scalar: @yaml_scalar_node ref, + int style: int ref, + varchar(900) value: string ref); + +yaml_errors (unique int id: @yaml_error, + varchar(900) message: string ref); + +yaml_locations(unique int locatable: @yaml_locatable ref, + int location: @location_default ref); + +@yaml_locatable = @yaml_node | @yaml_error; + +/* XML Files */ + +xmlEncoding( + unique int id: @file ref, + varchar(900) encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + varchar(900) root: string ref, + varchar(900) publicId: string ref, + varchar(900) systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + varchar(900) name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + varchar(900) name: string ref, + varchar(3600) value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + varchar(900) prefixName: string ref, + varchar(900) URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + varchar(3600) text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +@dataflownode = @expr | @functiondeclstmt | @classdeclstmt | @namespacedeclaration | @enumdeclaration | @property; + +@optionalchainable = @callexpr | @propaccess; + +isOptionalChaining(int id: @optionalchainable ref); + +/* + * configuration files with key value pairs + */ + +configs( + unique int id: @config +); + +configNames( + unique int id: @configName, + int config: @config ref, + string name: string ref +); + +configValues( + unique int id: @configValue, + int config: @config ref, + string value: string ref +); + +configLocations( + int locatable: @configLocatable ref, + int location: @location_default ref +); + +@configLocatable = @config | @configName | @configValue; + +/** + * The time taken for the extraction of a file. + * This table contains non-deterministic content. + * + * The sum of the `time` column for each (`file`, `timerKind`) pair + * is the total time taken for extraction of `file`. The `extractionPhase` + * column provides a granular view of the extraction time of the file. + */ +extraction_time( + int file : @file ref, + // see `com.semmle.js.extractor.ExtractionMetrics.ExtractionPhase`. + int extractionPhase: int ref, + // 0 for the elapsed CPU time in nanoseconds, 1 for the elapsed wallclock time in nanoseconds + int timerKind: int ref, + float time: float ref +) + +/** + * Non-timing related data for the extraction of a single file. + * This table contains non-deterministic content. + */ +extraction_data( + int file : @file ref, + // the absolute path to the cache file + varchar(900) cacheFile: string ref, + boolean fromCache: boolean ref, + int length: int ref +) diff --git a/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/upgrade.properties b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/upgrade.properties new file mode 100644 index 00000000000..623175336d6 --- /dev/null +++ b/javascript/upgrades/96b0a386b6fb8da2b5f2514f26154a10c906f9c5/upgrade.properties @@ -0,0 +1,2 @@ +description: add TypeScript symbols to import declarations +compatibility: backwards From 3c1cbcefa5aef7f9d5f34b3ca13f62fb715f5339 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Mon, 3 Feb 2020 10:24:57 +0000 Subject: [PATCH 124/148] TS: Pass virtual source root explicitly to Node.js process --- javascript/extractor/lib/typescript/src/main.ts | 4 +++- .../src/com/semmle/js/extractor/AutoBuild.java | 2 +- .../js/extractor/DependencyInstallationResult.java | 14 +++++++++++++- .../src/com/semmle/js/parser/TypeScriptParser.java | 4 ++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index 246a59fb4d1..cd2a112ef83 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -48,6 +48,7 @@ interface ParseCommand { interface OpenProjectCommand { command: "open-project"; tsConfig: string; + virtualSourceRoot: string | null; packageEntryPoints: [string, string][]; packageJsonFiles: [string, string][]; } @@ -261,7 +262,7 @@ function handleOpenProjectCommand(command: OpenProjectCommand) { let packageEntryPoints = new Map(command.packageEntryPoints); let packageJsonFiles = new Map(command.packageJsonFiles); - let virtualSourceRoot = new VirtualSourceRoot(process.cwd(), process.env["CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR"]); + let virtualSourceRoot = new VirtualSourceRoot(process.cwd(), command.virtualSourceRoot); /** * Rewrites path segments of form `node_modules/PACK/suffix` to be relative to @@ -582,6 +583,7 @@ if (process.argv.length > 2) { tsConfig: argument, packageEntryPoints: [], packageJsonFiles: [], + virtualSourceRoot: null, }); for (let sf of state.project.program.getSourceFiles()) { if (pathlib.basename(sf.fileName) === "lib.d.ts") continue; diff --git a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java index d3114360c7e..35295ae0678 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java +++ b/javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java @@ -784,7 +784,7 @@ public class AutoBuild { } } - return new DependencyInstallationResult(packageMainFile, packagesInRepo); + return new DependencyInstallationResult(virtualSourceRoot, packageMainFile, packagesInRepo); } /** diff --git a/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java b/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java index 5dd6bd60b6a..460a6573f6b 100644 --- a/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java +++ b/javascript/extractor/src/com/semmle/js/extractor/DependencyInstallationResult.java @@ -6,19 +6,31 @@ import java.util.Map; /** Contains the results of installing dependencies. */ public class DependencyInstallationResult { + private Path virtualSourceRoot; private Map packageEntryPoints; private Map packageJsonFiles; public static final DependencyInstallationResult empty = - new DependencyInstallationResult(Collections.emptyMap(), Collections.emptyMap()); + new DependencyInstallationResult(null, Collections.emptyMap(), Collections.emptyMap()); public DependencyInstallationResult( + Path virtualSourceRoot, Map packageEntryPoints, Map packageJsonFiles) { this.packageEntryPoints = packageEntryPoints; this.packageJsonFiles = packageJsonFiles; } + /** + * Returns the virtual source root or null if no virtual source root exists. + * + * The virtual source root is a directory hierarchy that mirrors the real source + * root, where dependencies are installed. + */ + public Path getVirtualSourceRoot() { + return virtualSourceRoot; + } + /** * Returns the mapping from package names to the TypeScript file that should * act as its main entry point. diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java index 27ed0e720fb..0f43a2da0b4 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java @@ -20,6 +20,7 @@ import java.util.Map; import com.google.gson.JsonArray; import com.google.gson.JsonElement; +import com.google.gson.JsonNull; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; @@ -435,6 +436,9 @@ public class TypeScriptParser { request.add("tsConfig", new JsonPrimitive(tsConfigFile.getPath())); request.add("packageEntryPoints", mapToArray(deps.getPackageEntryPoints())); request.add("packageJsonFiles", mapToArray(deps.getPackageJsonFiles())); + request.add("virtualSourceRoot", deps.getVirtualSourceRoot() == null + ? JsonNull.INSTANCE + : new JsonPrimitive(deps.getVirtualSourceRoot().toString())); JsonObject response = talkToParserWrapper(request); try { checkResponseType(response, "project-opened"); From 183dd68d6a2cfde52e4cfa106064bc9ff1c9cf82 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 3 Feb 2020 14:23:27 +0100 Subject: [PATCH 125/148] add qldoc to isPrivateField --- javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll | 3 +++ 1 file changed, 3 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll index aba2e3b5bb9..4bfbfb98017 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll @@ -536,6 +536,9 @@ module DataFlow { pragma[noinline] predicate accesses(Node base, string p) { getBase() = base and getPropertyName() = p } + /** + * Holds if this data flow node reads or writes a private field in a class. + */ predicate isPrivateField() { getPropertyName().charAt(0) = "#" and getPropertyNameExpr() instanceof Label } From e3189aaa4785a3a43f8fac183606603284597a71 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 3 Feb 2020 16:00:25 +0100 Subject: [PATCH 126/148] raise syntax error on declaration of private method, and add syntax tests for private fields --- .../src/com/semmle/jcorn/Parser.java | 3 ++ .../library-tests/Classes/privateFields.js | 5 +++ .../test/library-tests/Classes/tests.expected | 43 +++++++++++-------- .../SyntaxError/SyntaxError.expected | 2 + .../SyntaxError/destructingPrivate.js | 5 +++ .../SyntaxError/privateMethod.js | 3 ++ 6 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/destructingPrivate.js create mode 100644 javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/privateMethod.js diff --git a/javascript/extractor/src/com/semmle/jcorn/Parser.java b/javascript/extractor/src/com/semmle/jcorn/Parser.java index 7e344613b2e..eee2e172a6d 100644 --- a/javascript/extractor/src/com/semmle/jcorn/Parser.java +++ b/javascript/extractor/src/com/semmle/jcorn/Parser.java @@ -3238,6 +3238,9 @@ public class Parser { if (pi.kind.equals("set") && node.getValue().hasRest()) this.raiseRecoverable(params.get(params.size() - 1), "Setter cannot use rest params"); } + if (pi.key instanceof Identifier && ((Identifier)pi.key).getName().startsWith("#")) { + raiseRecoverable(pi.key, "Only fields, not methods, can be declared private."); + } return node; } diff --git a/javascript/ql/test/library-tests/Classes/privateFields.js b/javascript/ql/test/library-tests/Classes/privateFields.js index 169482e5826..5f108190bc3 100644 --- a/javascript/ql/test/library-tests/Classes/privateFields.js +++ b/javascript/ql/test/library-tests/Classes/privateFields.js @@ -19,4 +19,9 @@ class Foo { #privSecond; // is a PropNode, not a PropRef. Doesn't matter. ["#publicField"] = 6; + + calls() { + this.#privDecl(); + new this.#privDecl(); + } } \ No newline at end of file diff --git a/javascript/ql/test/library-tests/Classes/tests.expected b/javascript/ql/test/library-tests/Classes/tests.expected index 664a82e4422..e200256b70c 100644 --- a/javascript/ql/test/library-tests/Classes/tests.expected +++ b/javascript/ql/test/library-tests/Classes/tests.expected @@ -23,7 +23,7 @@ test_ClassDefinitions | fields.js:1:1:4:1 | class C ... = 42\\n} | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | | tst.js:1:9:4:1 | class { ... */ }\\n} | | tst.js:6:1:8:1 | class B ... t); }\\n} | @@ -43,7 +43,7 @@ test_ClassDefinition_getName | fields.js:1:1:4:1 | class C ... = 42\\n} | C | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | Point | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | ColouredPoint | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | Foo | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | Foo | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | MyClass | | tst.js:1:9:4:1 | class { ... */ }\\n} | A | | tst.js:6:1:8:1 | class B ... t); }\\n} | B | @@ -60,10 +60,11 @@ test_MethodDefinitions | points.js:21:3:24:3 | constru ... c;\\n } | points.js:21:3:21:13 | constructor | points.js:21:14:24:3 | (x, y, ... c;\\n } | points.js:20:1:33:1 | class C ... ;\\n }\\n} | | points.js:26:3:28:3 | toStrin ... ur;\\n } | points.js:26:3:26:10 | toString | points.js:26:11:28:3 | () {\\n ... ur;\\n } | points.js:20:1:33:1 | class C ... ;\\n }\\n} | | points.js:30:3:32:3 | static ... t";\\n } | points.js:30:10:30:18 | className | points.js:30:19:32:3 | () {\\n ... t";\\n } | points.js:20:1:33:1 | class C ... ;\\n }\\n} | -| privateFields.js:1:11:1:10 | constructor() {} | privateFields.js:1:11:1:10 | constructor | privateFields.js:1:11:1:10 | () {} | privateFields.js:1:1:22:1 | class F ... = 6;\\n} | -| privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | privateFields.js:4:2:4:6 | reads | privateFields.js:4:7:8:2 | () {\\n\\t\\t ... #if;\\n\\t} | privateFields.js:1:1:22:1 | class F ... = 6;\\n} | -| privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | privateFields.js:10:2:10:7 | equals | privateFields.js:10:8:12:2 | (o) {\\n\\t ... ecl;\\n\\t} | privateFields.js:1:1:22:1 | class F ... = 6;\\n} | -| privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | privateFields.js:14:2:14:7 | writes | privateFields.js:14:8:17:2 | () {\\n\\t\\t ... = 5;\\n\\t} | privateFields.js:1:1:22:1 | class F ... = 6;\\n} | +| privateFields.js:1:11:1:10 | constructor() {} | privateFields.js:1:11:1:10 | constructor | privateFields.js:1:11:1:10 | () {} | privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | +| privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | privateFields.js:4:2:4:6 | reads | privateFields.js:4:7:8:2 | () {\\n\\t\\t ... #if;\\n\\t} | privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | +| privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | privateFields.js:10:2:10:7 | equals | privateFields.js:10:8:12:2 | (o) {\\n\\t ... ecl;\\n\\t} | privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | +| privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | privateFields.js:14:2:14:7 | writes | privateFields.js:14:8:17:2 | () {\\n\\t\\t ... = 5;\\n\\t} | privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | +| privateFields.js:23:2:26:2 | calls() ... l();\\n\\t} | privateFields.js:23:2:23:6 | calls | privateFields.js:23:7:26:2 | () {\\n\\t\\t ... l();\\n\\t} | privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | | staticConstructor.js:1:15:1:14 | constructor() {} | staticConstructor.js:1:15:1:14 | constructor | staticConstructor.js:1:15:1:14 | () {} | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | | staticConstructor.js:2:3:2:59 | static ... tor"; } | staticConstructor.js:2:10:2:20 | constructor | staticConstructor.js:2:21:2:59 | () { re ... tor"; } | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | | tst.js:2:3:2:50 | "constr ... r. */ } | tst.js:2:3:2:15 | "constructor" | tst.js:2:16:2:50 | () { /* ... r. */ } | tst.js:1:9:4:1 | class { ... */ }\\n} | @@ -87,14 +88,15 @@ test_getAMember | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:21:3:24:3 | constru ... c;\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:26:3:28:3 | toStrin ... ur;\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:30:3:32:3 | static ... t";\\n } | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:1:11:1:10 | constructor() {} | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:2:2:2:15 | #privDecl = 3; | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:3:2:3:12 | #if = "if"; | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:19:2:19:13 | #privSecond; | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:21:2:21:22 | ["#publ ... "] = 6; | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:1:11:1:10 | constructor() {} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:2:2:2:15 | #privDecl = 3; | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:3:2:3:12 | #if = "if"; | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:19:2:19:13 | #privSecond; | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:21:2:21:22 | ["#publ ... "] = 6; | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:23:2:26:2 | calls() ... l();\\n\\t} | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | staticConstructor.js:1:15:1:14 | constructor() {} | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | staticConstructor.js:2:3:2:59 | static ... tor"; } | | tst.js:1:9:4:1 | class { ... */ }\\n} | tst.js:2:3:2:50 | "constr ... r. */ } | @@ -119,6 +121,7 @@ test_MethodNames | privateFields.js:4:2:8:2 | reads() ... #if;\\n\\t} | reads | | privateFields.js:10:2:12:2 | equals( ... ecl;\\n\\t} | equals | | privateFields.js:14:2:17:2 | writes( ... = 5;\\n\\t} | writes | +| privateFields.js:23:2:26:2 | calls() ... l();\\n\\t} | calls | | staticConstructor.js:1:15:1:14 | constructor() {} | constructor | | staticConstructor.js:2:3:2:59 | static ... tor"; } | constructor | | tst.js:2:3:2:50 | "constr ... r. */ } | constructor | @@ -153,7 +156,7 @@ test_ClassNodeConstructor | fields.js:1:1:4:1 | class C ... = 42\\n} | fields.js:1:9:1:8 | () {} | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | points.js:2:14:5:3 | (x, y) ... y;\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | points.js:21:14:24:3 | (x, y, ... c;\\n } | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | privateFields.js:1:11:1:10 | () {} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | privateFields.js:1:11:1:10 | () {} | | staticConstructor.js:1:1:3:1 | class M ... r"; }\\n} | staticConstructor.js:1:15:1:14 | () {} | | tst.js:1:9:4:1 | class { ... */ }\\n} | tst.js:2:16:2:50 | () { /* ... r. */ } | | tst.js:6:1:8:1 | class B ... t); }\\n} | tst.js:7:14:7:38 | () { su ... get); } | @@ -163,9 +166,10 @@ test_ClassNodeInstanceMethod | dataflow.js:4:2:13:2 | class F ... \\n\\t\\t}\\n\\t} | getPriv | dataflow.js:6:10:8:3 | () {\\n\\t\\t ... iv;\\n\\t\\t} | | points.js:1:1:18:1 | class P ... ;\\n }\\n} | toString | points.js:11:11:13:3 | () {\\n ... )";\\n } | | points.js:20:1:33:1 | class C ... ;\\n }\\n} | toString | points.js:26:11:28:3 | () {\\n ... ur;\\n } | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | equals | privateFields.js:10:8:12:2 | (o) {\\n\\t ... ecl;\\n\\t} | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | reads | privateFields.js:4:7:8:2 | () {\\n\\t\\t ... #if;\\n\\t} | -| privateFields.js:1:1:22:1 | class F ... = 6;\\n} | writes | privateFields.js:14:8:17:2 | () {\\n\\t\\t ... = 5;\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | calls | privateFields.js:23:7:26:2 | () {\\n\\t\\t ... l();\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | equals | privateFields.js:10:8:12:2 | (o) {\\n\\t ... ecl;\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | reads | privateFields.js:4:7:8:2 | () {\\n\\t\\t ... #if;\\n\\t} | +| privateFields.js:1:1:27:1 | class F ... );\\n\\t}\\n} | writes | privateFields.js:14:8:17:2 | () {\\n\\t\\t ... = 5;\\n\\t} | | tst.js:1:9:4:1 | class { ... */ }\\n} | constructor | tst.js:3:18:3:56 | () { /* ... r. */ } | | tst.js:11:1:14:1 | class C ... () {}\\n} | m | tst.js:12:4:12:8 | () {} | getAccessModifier @@ -212,6 +216,9 @@ getAccessModifier | privateFields.js:15:3:15:16 | this.#privDecl | privateFields.js:15:8:15:16 | #privDecl | Private | | privateFields.js:16:3:16:17 | this["#public"] | privateFields.js:16:8:16:16 | "#public" | Public | | privateFields.js:21:2:21:22 | ["#publ ... "] = 6; | privateFields.js:21:3:21:16 | "#publicField" | Public | +| privateFields.js:23:2:26:2 | calls() ... l();\\n\\t} | privateFields.js:23:2:23:6 | calls | Public | +| privateFields.js:24:3:24:16 | this.#privDecl | privateFields.js:24:8:24:16 | #privDecl | Private | +| privateFields.js:25:7:25:20 | this.#privDecl | privateFields.js:25:12:25:20 | #privDecl | Private | | staticConstructor.js:1:15:1:14 | constructor() {} | staticConstructor.js:1:15:1:14 | constructor | Public | | staticConstructor.js:2:3:2:59 | static ... tor"; } | staticConstructor.js:2:10:2:20 | constructor | Public | | staticConstructor.js:4:1:4:11 | console.log | staticConstructor.js:4:9:4:11 | log | Public | diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected index 575b5e21e7c..af30b4bbb35 100644 --- a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected +++ b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected @@ -1,2 +1,4 @@ | arrows.js:1:5:1:5 | Error: Argument name clash | Error: Argument name clash | +| destructingPrivate.js:3:6:3:6 | Error: Unexpected token | Error: Unexpected token | +| privateMethod.js:2:3:2:3 | Error: Only fields, not methods, can be declared private. | Error: Only fields, not methods, can be declared private. | | tst.js:2:12:2:12 | Error: Unterminated string constant | Error: Unterminated string constant | diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/destructingPrivate.js b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/destructingPrivate.js new file mode 100644 index 00000000000..45427b4203e --- /dev/null +++ b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/destructingPrivate.js @@ -0,0 +1,5 @@ +class C { + bar() { + {#privDecl} = this; + } +} \ No newline at end of file diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/privateMethod.js b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/privateMethod.js new file mode 100644 index 00000000000..3236346841b --- /dev/null +++ b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/privateMethod.js @@ -0,0 +1,3 @@ +class C { + #privateMethod() {} +} \ No newline at end of file From a8b3bcb87d548b35a5817ce82d3dd62505b10ed0 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 3 Feb 2020 16:13:32 +0100 Subject: [PATCH 127/148] C++: Indirection for value numbering --- .../valuenumbering/GlobalValueNumbering.qll | 609 +----------------- .../GlobalValueNumberingImpl.qll | 608 +++++++++++++++++ 2 files changed, 609 insertions(+), 608 deletions(-) create mode 100644 cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumberingImpl.qll diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll index f9231e24725..15e89a15390 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll @@ -1,608 +1 @@ -/** - * Provides an implementation of Global Value Numbering. - * See https://en.wikipedia.org/wiki/Global_value_numbering - * - * The predicate `globalValueNumber` converts an expression into a `GVN`, - * which is an abstract type representing the value of the expression. If - * two expressions have the same `GVN` then they compute the same value. - * For example: - * - * ``` - * void f(int x, int y) { - * g(x+y, x+y); - * } - * ``` - * - * In this example, both arguments in the call to `g` compute the same value, - * so both arguments have the same `GVN`. In other words, we can find - * this call with the following query: - * - * ``` - * from FunctionCall call, GVN v - * where v = globalValueNumber(call.getArgument(0)) - * and v = globalValueNumber(call.getArgument(1)) - * select call - * ``` - * - * The analysis is conservative, so two expressions might have different - * `GVN`s even though the actually always compute the same value. The most - * common reason for this is that the analysis cannot prove that there - * are no side-effects that might cause the computed value to change. - */ - -/* - * Note to developers: the correctness of this module depends on the - * definitions of GVN, globalValueNumber, and analyzableExpr being kept in - * sync with each other. If you change this module then make sure that the - * change is symmetric across all three. - */ - -import cpp -private import semmle.code.cpp.controlflow.SSA - -/** - * Holds if the result is a control flow node that might change the - * value of any global variable. This is used in the implementation - * of `GVN_OtherVariable`, because we need to be quite conservative when - * we assign a value number to a global variable. For example: - * - * ``` - * x = g+1; - * dosomething(); - * y = g+1; - * ``` - * - * It is not safe to assign the same value number to both instances - * of `g+1` in this example, because the call to `dosomething` might - * change the value of `g`. - */ -private ControlFlowNode nodeWithPossibleSideEffect() { - result instanceof Call - or - // If the lhs of an assignment is not analyzable by SSA, then - // we need to treat the assignment as having a possible side-effect. - result instanceof Assignment and not result instanceof SsaDefinition - or - result instanceof CrementOperation and not result instanceof SsaDefinition - or - exists(LocalVariable v | - result = v.getInitializer().getExpr() and not result instanceof SsaDefinition - ) - or - result instanceof AsmStmt -} - -/** - * Gets the entry node of the control flow graph of which `node` is a - * member. - */ -cached -private ControlFlowNode getControlFlowEntry(ControlFlowNode node) { - result = node.getControlFlowScope().getEntryPoint() and - result.getASuccessor*() = node -} - -/** - * Holds if there is a control flow edge from `src` to `dst` or - * if `dst` is an expression with a possible side-effect. The idea - * is to treat side effects as entry points in the control flow - * graph so that we can use the dominator tree to find the most recent - * side-effect. - */ -private predicate sideEffectCFG(ControlFlowNode src, ControlFlowNode dst) { - src.getASuccessor() = dst - or - // Add an edge from the entry point to any node that might have a side - // effect. - dst = nodeWithPossibleSideEffect() and - src = getControlFlowEntry(dst) -} - -/** - * Holds if `dominator` is the immediate dominator of `node` in - * the side-effect CFG. - */ -private predicate iDomEffect(ControlFlowNode dominator, ControlFlowNode node) = - idominance(functionEntry/1, sideEffectCFG/2)(_, dominator, node) - -/** - * Gets the most recent side effect. To be more precise, `result` is a - * dominator of `node` and no side-effects can occur between `result` and - * `node`. - * - * `sideEffectCFG` has an edge from the function entry to every node with a - * side-effect. This means that every node with a side-effect has the - * function entry as its immediate dominator. So if node `x` dominates node - * `y` then there can be no side effects between `x` and `y` unless `x` is - * the function entry. So the optimal choice for `result` has the function - * entry as its immediate dominator. - * - * Example: - * - * ``` - * 000: int f(int a, int b, int *p) { - * 001: int r = 0; - * 002: if (a) { - * 003: if (b) { - * 004: sideEffect1(); - * 005: } - * 006: } else { - * 007: sideEffect2(); - * 008: } - * 009: if (a) { - * 010: r++; // Not a side-effect, because r is an SSA variable. - * 011: } - * 012: if (b) { - * 013: r++; // Not a side-effect, because r is an SSA variable. - * 014: } - * 015: return *p; - * 016: } - * ``` - * - * Suppose we want to find the most recent side-effect for the dereference - * of `p` on line 015. The `sideEffectCFG` has an edge from the function - * entry (line 000) to the side effects at lines 004 and 007. Therefore, - * the immediate dominator tree looks like this: - * - * 000 - 001 - 002 - 003 - * - 004 - * - 007 - * - 009 - 010 - * - 012 - 013 - * - 015 - * - * The immediate dominator path to line 015 is 000 - 009 - 012 - 015. - * Therefore, the most recent side effect for line 015 is line 009. - */ -cached -private ControlFlowNode mostRecentSideEffect(ControlFlowNode node) { - exists(ControlFlowNode entry | - functionEntry(entry) and - iDomEffect(entry, result) and - iDomEffect*(result, node) - ) -} - -/** Used to represent the "global value number" of an expression. */ -cached -private newtype GVNBase = - GVN_IntConst(int val, Type t) { mk_IntConst(val, t, _) } or - GVN_FloatConst(float val, Type t) { mk_FloatConst(val, t, _) } or - // If the local variable does not have a defining value, then - // we use the SsaDefinition as its global value number. - GVN_UndefinedStackVariable(StackVariable x, SsaDefinition def) { - mk_UndefinedStackVariable(x, def, _) - } or - // Variables with no SSA information. As a crude (but safe) - // approximation, we use `mostRecentSideEffect` to compute a definition - // location for the variable. This ensures that two instances of the same - // global variable will only get the same value number if they are - // guaranteed to have the same value. - GVN_OtherVariable(Variable x, ControlFlowNode dominator) { mk_OtherVariable(x, dominator, _) } or - GVN_FieldAccess(GVN s, Field f) { - mk_DotFieldAccess(s, f, _) or - mk_PointerFieldAccess_with_deref(s, f, _) or - mk_ImplicitThisFieldAccess_with_deref(s, f, _) - } or - // Dereference a pointer. The value might have changed since the last - // time the pointer was dereferenced, so we need to include a definition - // location. As a crude (but safe) approximation, we use - // `mostRecentSideEffect` to compute a definition location. - GVN_Deref(GVN p, ControlFlowNode dominator) { - mk_Deref(p, dominator, _) or - mk_PointerFieldAccess(p, _, dominator, _) or - mk_ImplicitThisFieldAccess_with_qualifier(p, _, dominator, _) - } or - GVN_ThisExpr(Function fcn) { - mk_ThisExpr(fcn, _) or - mk_ImplicitThisFieldAccess(fcn, _, _, _) - } or - GVN_Conversion(Type t, GVN child) { mk_Conversion(t, child, _) } or - GVN_BinaryOp(GVN lhs, GVN rhs, string opname) { mk_BinaryOp(lhs, rhs, opname, _) } or - GVN_UnaryOp(GVN child, string opname) { mk_UnaryOp(child, opname, _) } or - GVN_ArrayAccess(GVN x, GVN i, ControlFlowNode dominator) { mk_ArrayAccess(x, i, dominator, _) } or - // Any expression that is not handled by the cases above is - // given a unique number based on the expression itself. - GVN_Unanalyzable(Expr e) { not analyzableExpr(e) } - -/** - * A Global Value Number. A GVN is an abstract representation of the value - * computed by an expression. The relationship between `Expr` and `GVN` is - * many-to-one: every `Expr` has exactly one `GVN`, but multiple - * expressions can have the same `GVN`. If two expressions have the same - * `GVN`, it means that they compute the same value at run time. The `GVN` - * is an opaque value, so you cannot deduce what the run-time value of an - * expression will be from its `GVN`. The only use for the `GVN` of an - * expression is to find other expressions that compute the same value. - * Use the predicate `globalValueNumber` to get the `GVN` for an `Expr`. - * - * Note: `GVN` has `toString` and `getLocation` methods, so that it can be - * displayed in a results list. These work by picking an arbitrary - * expression with this `GVN` and using its `toString` and `getLocation` - * methods. - */ -class GVN extends GVNBase { - GVN() { this instanceof GVNBase } - - /** Gets an expression that has this GVN. */ - Expr getAnExpr() { this = globalValueNumber(result) } - - /** Gets the kind of the GVN. This can be useful for debugging. */ - string getKind() { - if this instanceof GVN_IntConst - then result = "IntConst" - else - if this instanceof GVN_FloatConst - then result = "FloatConst" - else - if this instanceof GVN_UndefinedStackVariable - then result = "UndefinedStackVariable" - else - if this instanceof GVN_OtherVariable - then result = "OtherVariable" - else - if this instanceof GVN_FieldAccess - then result = "FieldAccess" - else - if this instanceof GVN_Deref - then result = "Deref" - else - if this instanceof GVN_ThisExpr - then result = "ThisExpr" - else - if this instanceof GVN_Conversion - then result = "Conversion" - else - if this instanceof GVN_BinaryOp - then result = "BinaryOp" - else - if this instanceof GVN_UnaryOp - then result = "UnaryOp" - else - if this instanceof GVN_ArrayAccess - then result = "ArrayAccess" - else - if this instanceof GVN_Unanalyzable - then result = "Unanalyzable" - else result = "error" - } - - /** - * Gets an example of an expression with this GVN. - * This is useful for things like implementing toString(). - */ - private Expr exampleExpr() { - // Pick the expression with the minimum source location string. This is - // just an arbitrary way to pick an expression with this `GVN`. - result = min(Expr e | this = globalValueNumber(e) | e order by e.getLocation().toString()) - } - - /** Gets a textual representation of this element. */ - string toString() { result = exampleExpr().toString() } - - /** Gets the primary location of this element. */ - Location getLocation() { result = exampleExpr().getLocation() } -} - -private predicate analyzableIntConst(Expr e) { - strictcount(e.getValue().toInt()) = 1 and - strictcount(e.getUnspecifiedType()) = 1 -} - -private predicate mk_IntConst(int val, Type t, Expr e) { - analyzableIntConst(e) and - val = e.getValue().toInt() and - t = e.getUnspecifiedType() -} - -private predicate analyzableFloatConst(Expr e) { - strictcount(e.getValue().toFloat()) = 1 and - strictcount(e.getUnspecifiedType()) = 1 and - not analyzableIntConst(e) -} - -private predicate mk_FloatConst(float val, Type t, Expr e) { - analyzableFloatConst(e) and - val = e.getValue().toFloat() and - t = e.getUnspecifiedType() -} - -private predicate analyzableStackVariable(VariableAccess access) { - strictcount(SsaDefinition def | def.getAUse(_) = access | def) = 1 and - strictcount(SsaDefinition def, Variable v | def.getAUse(v) = access | v) = 1 and - count(SsaDefinition def, Variable v | - def.getAUse(v) = access - | - def.getDefiningValue(v).getFullyConverted() - ) <= 1 and - not analyzableConst(access) -} - -// Note: this predicate only has a result if the access has no -// defining value. If there is a defining value, then there is no -// need to generate a fresh `GVN` for the access because `globalValueNumber` -// will follow the chain and use the GVN of the defining value. -private predicate mk_UndefinedStackVariable( - StackVariable x, SsaDefinition def, VariableAccess access -) { - analyzableStackVariable(access) and - access = def.getAUse(x) and - not exists(def.getDefiningValue(x)) -} - -private predicate analyzableDotFieldAccess(DotFieldAccess access) { - strictcount(access.getTarget()) = 1 and - strictcount(access.getQualifier().getFullyConverted()) = 1 and - not analyzableConst(access) -} - -private predicate mk_DotFieldAccess(GVN qualifier, Field target, DotFieldAccess access) { - analyzableDotFieldAccess(access) and - target = access.getTarget() and - qualifier = globalValueNumber(access.getQualifier().getFullyConverted()) -} - -private predicate analyzablePointerFieldAccess(PointerFieldAccess access) { - strictcount(mostRecentSideEffect(access)) = 1 and - strictcount(access.getTarget()) = 1 and - strictcount(access.getQualifier().getFullyConverted()) = 1 and - not analyzableConst(access) -} - -private predicate mk_PointerFieldAccess( - GVN qualifier, Field target, ControlFlowNode dominator, PointerFieldAccess access -) { - analyzablePointerFieldAccess(access) and - dominator = mostRecentSideEffect(access) and - target = access.getTarget() and - qualifier = globalValueNumber(access.getQualifier().getFullyConverted()) -} - -/** - * `obj->field` is equivalent to `(*obj).field`, so we need to wrap an - * extra `GVN_Deref` around the qualifier. - */ -private predicate mk_PointerFieldAccess_with_deref( - GVN new_qualifier, Field target, PointerFieldAccess access -) { - exists(GVN qualifier, ControlFlowNode dominator | - mk_PointerFieldAccess(qualifier, target, dominator, access) and - new_qualifier = GVN_Deref(qualifier, dominator) - ) -} - -private predicate analyzableImplicitThisFieldAccess(ImplicitThisFieldAccess access) { - strictcount(mostRecentSideEffect(access)) = 1 and - strictcount(access.getTarget()) = 1 and - strictcount(access.getEnclosingFunction()) = 1 and - not analyzableConst(access) -} - -private predicate mk_ImplicitThisFieldAccess( - Function fcn, Field target, ControlFlowNode dominator, ImplicitThisFieldAccess access -) { - analyzableImplicitThisFieldAccess(access) and - dominator = mostRecentSideEffect(access) and - target = access.getTarget() and - fcn = access.getEnclosingFunction() -} - -private predicate mk_ImplicitThisFieldAccess_with_qualifier( - GVN qualifier, Field target, ControlFlowNode dominator, ImplicitThisFieldAccess access -) { - exists(Function fcn | - mk_ImplicitThisFieldAccess(fcn, target, dominator, access) and - qualifier = GVN_ThisExpr(fcn) - ) -} - -private predicate mk_ImplicitThisFieldAccess_with_deref( - GVN new_qualifier, Field target, ImplicitThisFieldAccess access -) { - exists(GVN qualifier, ControlFlowNode dominator | - mk_ImplicitThisFieldAccess_with_qualifier(qualifier, target, dominator, access) and - new_qualifier = GVN_Deref(qualifier, dominator) - ) -} - -/** - * Holds if `access` is an access of a variable that does - * not have SSA information. (For example, because the variable - * is global.) - */ -private predicate analyzableOtherVariable(VariableAccess access) { - not access instanceof FieldAccess and - not exists(SsaDefinition def | access = def.getAUse(_)) and - strictcount(access.getTarget()) = 1 and - strictcount(mostRecentSideEffect(access)) = 1 and - not analyzableConst(access) -} - -private predicate mk_OtherVariable(Variable x, ControlFlowNode dominator, VariableAccess access) { - analyzableOtherVariable(access) and - x = access.getTarget() and - dominator = mostRecentSideEffect(access) -} - -private predicate analyzableConversion(Conversion conv) { - strictcount(conv.getUnspecifiedType()) = 1 and - strictcount(conv.getExpr()) = 1 and - not analyzableConst(conv) -} - -private predicate mk_Conversion(Type t, GVN child, Conversion conv) { - analyzableConversion(conv) and - t = conv.getUnspecifiedType() and - child = globalValueNumber(conv.getExpr()) -} - -private predicate analyzableBinaryOp(BinaryOperation op) { - op.isPure() and - strictcount(op.getLeftOperand().getFullyConverted()) = 1 and - strictcount(op.getRightOperand().getFullyConverted()) = 1 and - strictcount(op.getOperator()) = 1 and - not analyzableConst(op) -} - -private predicate mk_BinaryOp(GVN lhs, GVN rhs, string opname, BinaryOperation op) { - analyzableBinaryOp(op) and - lhs = globalValueNumber(op.getLeftOperand().getFullyConverted()) and - rhs = globalValueNumber(op.getRightOperand().getFullyConverted()) and - opname = op.getOperator() -} - -private predicate analyzableUnaryOp(UnaryOperation op) { - not op instanceof PointerDereferenceExpr and - op.isPure() and - strictcount(op.getOperand().getFullyConverted()) = 1 and - strictcount(op.getOperator()) = 1 and - not analyzableConst(op) -} - -private predicate mk_UnaryOp(GVN child, string opname, UnaryOperation op) { - analyzableUnaryOp(op) and - child = globalValueNumber(op.getOperand().getFullyConverted()) and - opname = op.getOperator() -} - -private predicate analyzableThisExpr(ThisExpr thisExpr) { - strictcount(thisExpr.getEnclosingFunction()) = 1 and - not analyzableConst(thisExpr) -} - -private predicate mk_ThisExpr(Function fcn, ThisExpr thisExpr) { - analyzableThisExpr(thisExpr) and - fcn = thisExpr.getEnclosingFunction() -} - -private predicate analyzableArrayAccess(ArrayExpr ae) { - strictcount(ae.getArrayBase().getFullyConverted()) = 1 and - strictcount(ae.getArrayOffset().getFullyConverted()) = 1 and - strictcount(mostRecentSideEffect(ae)) = 1 and - not analyzableConst(ae) -} - -private predicate mk_ArrayAccess(GVN base, GVN offset, ControlFlowNode dominator, ArrayExpr ae) { - analyzableArrayAccess(ae) and - base = globalValueNumber(ae.getArrayBase().getFullyConverted()) and - offset = globalValueNumber(ae.getArrayOffset().getFullyConverted()) and - dominator = mostRecentSideEffect(ae) -} - -private predicate analyzablePointerDereferenceExpr(PointerDereferenceExpr deref) { - strictcount(deref.getOperand().getFullyConverted()) = 1 and - strictcount(mostRecentSideEffect(deref)) = 1 and - not analyzableConst(deref) -} - -private predicate mk_Deref(GVN p, ControlFlowNode dominator, PointerDereferenceExpr deref) { - analyzablePointerDereferenceExpr(deref) and - p = globalValueNumber(deref.getOperand().getFullyConverted()) and - dominator = mostRecentSideEffect(deref) -} - -/** Gets the global value number of expression `e`. */ -cached -GVN globalValueNumber(Expr e) { - exists(int val, Type t | - mk_IntConst(val, t, e) and - result = GVN_IntConst(val, t) - ) - or - exists(float val, Type t | - mk_FloatConst(val, t, e) and - result = GVN_FloatConst(val, t) - ) - or - // Local variable with a defining value. - exists(StackVariable x, SsaDefinition def | - analyzableStackVariable(e) and - e = def.getAUse(x) and - result = globalValueNumber(def.getDefiningValue(x).getFullyConverted()) - ) - or - // Local variable without a defining value. - exists(StackVariable x, SsaDefinition def | - mk_UndefinedStackVariable(x, def, e) and - result = GVN_UndefinedStackVariable(x, def) - ) - or - // Variable with no SSA information. - exists(Variable x, ControlFlowNode dominator | - mk_OtherVariable(x, dominator, e) and - result = GVN_OtherVariable(x, dominator) - ) - or - exists(GVN qualifier, Field target | - mk_DotFieldAccess(qualifier, target, e) and - result = GVN_FieldAccess(qualifier, target) - ) - or - exists(GVN qualifier, Field target | - mk_PointerFieldAccess_with_deref(qualifier, target, e) and - result = GVN_FieldAccess(qualifier, target) - ) - or - exists(GVN qualifier, Field target | - mk_ImplicitThisFieldAccess_with_deref(qualifier, target, e) and - result = GVN_FieldAccess(qualifier, target) - ) - or - exists(Function fcn | - mk_ThisExpr(fcn, e) and - result = GVN_ThisExpr(fcn) - ) - or - exists(Type t, GVN child | - mk_Conversion(t, child, e) and - result = GVN_Conversion(t, child) - ) - or - exists(GVN lhs, GVN rhs, string opname | - mk_BinaryOp(lhs, rhs, opname, e) and - result = GVN_BinaryOp(lhs, rhs, opname) - ) - or - exists(GVN child, string opname | - mk_UnaryOp(child, opname, e) and - result = GVN_UnaryOp(child, opname) - ) - or - exists(GVN x, GVN i, ControlFlowNode dominator | - mk_ArrayAccess(x, i, dominator, e) and - result = GVN_ArrayAccess(x, i, dominator) - ) - or - exists(GVN p, ControlFlowNode dominator | - mk_Deref(p, dominator, e) and - result = GVN_Deref(p, dominator) - ) - or - not analyzableExpr(e) and result = GVN_Unanalyzable(e) -} - -private predicate analyzableConst(Expr e) { - analyzableIntConst(e) or - analyzableFloatConst(e) -} - -/** - * Holds if the expression is explicitly handled by `globalValueNumber`. - * Unanalyzable expressions still need to be given a global value number, - * but it will be a unique number that is not shared with any other - * expression. - */ -private predicate analyzableExpr(Expr e) { - analyzableConst(e) or - analyzableStackVariable(e) or - analyzableDotFieldAccess(e) or - analyzablePointerFieldAccess(e) or - analyzableImplicitThisFieldAccess(e) or - analyzableOtherVariable(e) or - analyzableConversion(e) or - analyzableBinaryOp(e) or - analyzableUnaryOp(e) or - analyzableThisExpr(e) or - analyzableArrayAccess(e) or - analyzablePointerDereferenceExpr(e) -} +import GlobalValueNumberingImpl \ No newline at end of file diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumberingImpl.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumberingImpl.qll new file mode 100644 index 00000000000..f9231e24725 --- /dev/null +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumberingImpl.qll @@ -0,0 +1,608 @@ +/** + * Provides an implementation of Global Value Numbering. + * See https://en.wikipedia.org/wiki/Global_value_numbering + * + * The predicate `globalValueNumber` converts an expression into a `GVN`, + * which is an abstract type representing the value of the expression. If + * two expressions have the same `GVN` then they compute the same value. + * For example: + * + * ``` + * void f(int x, int y) { + * g(x+y, x+y); + * } + * ``` + * + * In this example, both arguments in the call to `g` compute the same value, + * so both arguments have the same `GVN`. In other words, we can find + * this call with the following query: + * + * ``` + * from FunctionCall call, GVN v + * where v = globalValueNumber(call.getArgument(0)) + * and v = globalValueNumber(call.getArgument(1)) + * select call + * ``` + * + * The analysis is conservative, so two expressions might have different + * `GVN`s even though the actually always compute the same value. The most + * common reason for this is that the analysis cannot prove that there + * are no side-effects that might cause the computed value to change. + */ + +/* + * Note to developers: the correctness of this module depends on the + * definitions of GVN, globalValueNumber, and analyzableExpr being kept in + * sync with each other. If you change this module then make sure that the + * change is symmetric across all three. + */ + +import cpp +private import semmle.code.cpp.controlflow.SSA + +/** + * Holds if the result is a control flow node that might change the + * value of any global variable. This is used in the implementation + * of `GVN_OtherVariable`, because we need to be quite conservative when + * we assign a value number to a global variable. For example: + * + * ``` + * x = g+1; + * dosomething(); + * y = g+1; + * ``` + * + * It is not safe to assign the same value number to both instances + * of `g+1` in this example, because the call to `dosomething` might + * change the value of `g`. + */ +private ControlFlowNode nodeWithPossibleSideEffect() { + result instanceof Call + or + // If the lhs of an assignment is not analyzable by SSA, then + // we need to treat the assignment as having a possible side-effect. + result instanceof Assignment and not result instanceof SsaDefinition + or + result instanceof CrementOperation and not result instanceof SsaDefinition + or + exists(LocalVariable v | + result = v.getInitializer().getExpr() and not result instanceof SsaDefinition + ) + or + result instanceof AsmStmt +} + +/** + * Gets the entry node of the control flow graph of which `node` is a + * member. + */ +cached +private ControlFlowNode getControlFlowEntry(ControlFlowNode node) { + result = node.getControlFlowScope().getEntryPoint() and + result.getASuccessor*() = node +} + +/** + * Holds if there is a control flow edge from `src` to `dst` or + * if `dst` is an expression with a possible side-effect. The idea + * is to treat side effects as entry points in the control flow + * graph so that we can use the dominator tree to find the most recent + * side-effect. + */ +private predicate sideEffectCFG(ControlFlowNode src, ControlFlowNode dst) { + src.getASuccessor() = dst + or + // Add an edge from the entry point to any node that might have a side + // effect. + dst = nodeWithPossibleSideEffect() and + src = getControlFlowEntry(dst) +} + +/** + * Holds if `dominator` is the immediate dominator of `node` in + * the side-effect CFG. + */ +private predicate iDomEffect(ControlFlowNode dominator, ControlFlowNode node) = + idominance(functionEntry/1, sideEffectCFG/2)(_, dominator, node) + +/** + * Gets the most recent side effect. To be more precise, `result` is a + * dominator of `node` and no side-effects can occur between `result` and + * `node`. + * + * `sideEffectCFG` has an edge from the function entry to every node with a + * side-effect. This means that every node with a side-effect has the + * function entry as its immediate dominator. So if node `x` dominates node + * `y` then there can be no side effects between `x` and `y` unless `x` is + * the function entry. So the optimal choice for `result` has the function + * entry as its immediate dominator. + * + * Example: + * + * ``` + * 000: int f(int a, int b, int *p) { + * 001: int r = 0; + * 002: if (a) { + * 003: if (b) { + * 004: sideEffect1(); + * 005: } + * 006: } else { + * 007: sideEffect2(); + * 008: } + * 009: if (a) { + * 010: r++; // Not a side-effect, because r is an SSA variable. + * 011: } + * 012: if (b) { + * 013: r++; // Not a side-effect, because r is an SSA variable. + * 014: } + * 015: return *p; + * 016: } + * ``` + * + * Suppose we want to find the most recent side-effect for the dereference + * of `p` on line 015. The `sideEffectCFG` has an edge from the function + * entry (line 000) to the side effects at lines 004 and 007. Therefore, + * the immediate dominator tree looks like this: + * + * 000 - 001 - 002 - 003 + * - 004 + * - 007 + * - 009 - 010 + * - 012 - 013 + * - 015 + * + * The immediate dominator path to line 015 is 000 - 009 - 012 - 015. + * Therefore, the most recent side effect for line 015 is line 009. + */ +cached +private ControlFlowNode mostRecentSideEffect(ControlFlowNode node) { + exists(ControlFlowNode entry | + functionEntry(entry) and + iDomEffect(entry, result) and + iDomEffect*(result, node) + ) +} + +/** Used to represent the "global value number" of an expression. */ +cached +private newtype GVNBase = + GVN_IntConst(int val, Type t) { mk_IntConst(val, t, _) } or + GVN_FloatConst(float val, Type t) { mk_FloatConst(val, t, _) } or + // If the local variable does not have a defining value, then + // we use the SsaDefinition as its global value number. + GVN_UndefinedStackVariable(StackVariable x, SsaDefinition def) { + mk_UndefinedStackVariable(x, def, _) + } or + // Variables with no SSA information. As a crude (but safe) + // approximation, we use `mostRecentSideEffect` to compute a definition + // location for the variable. This ensures that two instances of the same + // global variable will only get the same value number if they are + // guaranteed to have the same value. + GVN_OtherVariable(Variable x, ControlFlowNode dominator) { mk_OtherVariable(x, dominator, _) } or + GVN_FieldAccess(GVN s, Field f) { + mk_DotFieldAccess(s, f, _) or + mk_PointerFieldAccess_with_deref(s, f, _) or + mk_ImplicitThisFieldAccess_with_deref(s, f, _) + } or + // Dereference a pointer. The value might have changed since the last + // time the pointer was dereferenced, so we need to include a definition + // location. As a crude (but safe) approximation, we use + // `mostRecentSideEffect` to compute a definition location. + GVN_Deref(GVN p, ControlFlowNode dominator) { + mk_Deref(p, dominator, _) or + mk_PointerFieldAccess(p, _, dominator, _) or + mk_ImplicitThisFieldAccess_with_qualifier(p, _, dominator, _) + } or + GVN_ThisExpr(Function fcn) { + mk_ThisExpr(fcn, _) or + mk_ImplicitThisFieldAccess(fcn, _, _, _) + } or + GVN_Conversion(Type t, GVN child) { mk_Conversion(t, child, _) } or + GVN_BinaryOp(GVN lhs, GVN rhs, string opname) { mk_BinaryOp(lhs, rhs, opname, _) } or + GVN_UnaryOp(GVN child, string opname) { mk_UnaryOp(child, opname, _) } or + GVN_ArrayAccess(GVN x, GVN i, ControlFlowNode dominator) { mk_ArrayAccess(x, i, dominator, _) } or + // Any expression that is not handled by the cases above is + // given a unique number based on the expression itself. + GVN_Unanalyzable(Expr e) { not analyzableExpr(e) } + +/** + * A Global Value Number. A GVN is an abstract representation of the value + * computed by an expression. The relationship between `Expr` and `GVN` is + * many-to-one: every `Expr` has exactly one `GVN`, but multiple + * expressions can have the same `GVN`. If two expressions have the same + * `GVN`, it means that they compute the same value at run time. The `GVN` + * is an opaque value, so you cannot deduce what the run-time value of an + * expression will be from its `GVN`. The only use for the `GVN` of an + * expression is to find other expressions that compute the same value. + * Use the predicate `globalValueNumber` to get the `GVN` for an `Expr`. + * + * Note: `GVN` has `toString` and `getLocation` methods, so that it can be + * displayed in a results list. These work by picking an arbitrary + * expression with this `GVN` and using its `toString` and `getLocation` + * methods. + */ +class GVN extends GVNBase { + GVN() { this instanceof GVNBase } + + /** Gets an expression that has this GVN. */ + Expr getAnExpr() { this = globalValueNumber(result) } + + /** Gets the kind of the GVN. This can be useful for debugging. */ + string getKind() { + if this instanceof GVN_IntConst + then result = "IntConst" + else + if this instanceof GVN_FloatConst + then result = "FloatConst" + else + if this instanceof GVN_UndefinedStackVariable + then result = "UndefinedStackVariable" + else + if this instanceof GVN_OtherVariable + then result = "OtherVariable" + else + if this instanceof GVN_FieldAccess + then result = "FieldAccess" + else + if this instanceof GVN_Deref + then result = "Deref" + else + if this instanceof GVN_ThisExpr + then result = "ThisExpr" + else + if this instanceof GVN_Conversion + then result = "Conversion" + else + if this instanceof GVN_BinaryOp + then result = "BinaryOp" + else + if this instanceof GVN_UnaryOp + then result = "UnaryOp" + else + if this instanceof GVN_ArrayAccess + then result = "ArrayAccess" + else + if this instanceof GVN_Unanalyzable + then result = "Unanalyzable" + else result = "error" + } + + /** + * Gets an example of an expression with this GVN. + * This is useful for things like implementing toString(). + */ + private Expr exampleExpr() { + // Pick the expression with the minimum source location string. This is + // just an arbitrary way to pick an expression with this `GVN`. + result = min(Expr e | this = globalValueNumber(e) | e order by e.getLocation().toString()) + } + + /** Gets a textual representation of this element. */ + string toString() { result = exampleExpr().toString() } + + /** Gets the primary location of this element. */ + Location getLocation() { result = exampleExpr().getLocation() } +} + +private predicate analyzableIntConst(Expr e) { + strictcount(e.getValue().toInt()) = 1 and + strictcount(e.getUnspecifiedType()) = 1 +} + +private predicate mk_IntConst(int val, Type t, Expr e) { + analyzableIntConst(e) and + val = e.getValue().toInt() and + t = e.getUnspecifiedType() +} + +private predicate analyzableFloatConst(Expr e) { + strictcount(e.getValue().toFloat()) = 1 and + strictcount(e.getUnspecifiedType()) = 1 and + not analyzableIntConst(e) +} + +private predicate mk_FloatConst(float val, Type t, Expr e) { + analyzableFloatConst(e) and + val = e.getValue().toFloat() and + t = e.getUnspecifiedType() +} + +private predicate analyzableStackVariable(VariableAccess access) { + strictcount(SsaDefinition def | def.getAUse(_) = access | def) = 1 and + strictcount(SsaDefinition def, Variable v | def.getAUse(v) = access | v) = 1 and + count(SsaDefinition def, Variable v | + def.getAUse(v) = access + | + def.getDefiningValue(v).getFullyConverted() + ) <= 1 and + not analyzableConst(access) +} + +// Note: this predicate only has a result if the access has no +// defining value. If there is a defining value, then there is no +// need to generate a fresh `GVN` for the access because `globalValueNumber` +// will follow the chain and use the GVN of the defining value. +private predicate mk_UndefinedStackVariable( + StackVariable x, SsaDefinition def, VariableAccess access +) { + analyzableStackVariable(access) and + access = def.getAUse(x) and + not exists(def.getDefiningValue(x)) +} + +private predicate analyzableDotFieldAccess(DotFieldAccess access) { + strictcount(access.getTarget()) = 1 and + strictcount(access.getQualifier().getFullyConverted()) = 1 and + not analyzableConst(access) +} + +private predicate mk_DotFieldAccess(GVN qualifier, Field target, DotFieldAccess access) { + analyzableDotFieldAccess(access) and + target = access.getTarget() and + qualifier = globalValueNumber(access.getQualifier().getFullyConverted()) +} + +private predicate analyzablePointerFieldAccess(PointerFieldAccess access) { + strictcount(mostRecentSideEffect(access)) = 1 and + strictcount(access.getTarget()) = 1 and + strictcount(access.getQualifier().getFullyConverted()) = 1 and + not analyzableConst(access) +} + +private predicate mk_PointerFieldAccess( + GVN qualifier, Field target, ControlFlowNode dominator, PointerFieldAccess access +) { + analyzablePointerFieldAccess(access) and + dominator = mostRecentSideEffect(access) and + target = access.getTarget() and + qualifier = globalValueNumber(access.getQualifier().getFullyConverted()) +} + +/** + * `obj->field` is equivalent to `(*obj).field`, so we need to wrap an + * extra `GVN_Deref` around the qualifier. + */ +private predicate mk_PointerFieldAccess_with_deref( + GVN new_qualifier, Field target, PointerFieldAccess access +) { + exists(GVN qualifier, ControlFlowNode dominator | + mk_PointerFieldAccess(qualifier, target, dominator, access) and + new_qualifier = GVN_Deref(qualifier, dominator) + ) +} + +private predicate analyzableImplicitThisFieldAccess(ImplicitThisFieldAccess access) { + strictcount(mostRecentSideEffect(access)) = 1 and + strictcount(access.getTarget()) = 1 and + strictcount(access.getEnclosingFunction()) = 1 and + not analyzableConst(access) +} + +private predicate mk_ImplicitThisFieldAccess( + Function fcn, Field target, ControlFlowNode dominator, ImplicitThisFieldAccess access +) { + analyzableImplicitThisFieldAccess(access) and + dominator = mostRecentSideEffect(access) and + target = access.getTarget() and + fcn = access.getEnclosingFunction() +} + +private predicate mk_ImplicitThisFieldAccess_with_qualifier( + GVN qualifier, Field target, ControlFlowNode dominator, ImplicitThisFieldAccess access +) { + exists(Function fcn | + mk_ImplicitThisFieldAccess(fcn, target, dominator, access) and + qualifier = GVN_ThisExpr(fcn) + ) +} + +private predicate mk_ImplicitThisFieldAccess_with_deref( + GVN new_qualifier, Field target, ImplicitThisFieldAccess access +) { + exists(GVN qualifier, ControlFlowNode dominator | + mk_ImplicitThisFieldAccess_with_qualifier(qualifier, target, dominator, access) and + new_qualifier = GVN_Deref(qualifier, dominator) + ) +} + +/** + * Holds if `access` is an access of a variable that does + * not have SSA information. (For example, because the variable + * is global.) + */ +private predicate analyzableOtherVariable(VariableAccess access) { + not access instanceof FieldAccess and + not exists(SsaDefinition def | access = def.getAUse(_)) and + strictcount(access.getTarget()) = 1 and + strictcount(mostRecentSideEffect(access)) = 1 and + not analyzableConst(access) +} + +private predicate mk_OtherVariable(Variable x, ControlFlowNode dominator, VariableAccess access) { + analyzableOtherVariable(access) and + x = access.getTarget() and + dominator = mostRecentSideEffect(access) +} + +private predicate analyzableConversion(Conversion conv) { + strictcount(conv.getUnspecifiedType()) = 1 and + strictcount(conv.getExpr()) = 1 and + not analyzableConst(conv) +} + +private predicate mk_Conversion(Type t, GVN child, Conversion conv) { + analyzableConversion(conv) and + t = conv.getUnspecifiedType() and + child = globalValueNumber(conv.getExpr()) +} + +private predicate analyzableBinaryOp(BinaryOperation op) { + op.isPure() and + strictcount(op.getLeftOperand().getFullyConverted()) = 1 and + strictcount(op.getRightOperand().getFullyConverted()) = 1 and + strictcount(op.getOperator()) = 1 and + not analyzableConst(op) +} + +private predicate mk_BinaryOp(GVN lhs, GVN rhs, string opname, BinaryOperation op) { + analyzableBinaryOp(op) and + lhs = globalValueNumber(op.getLeftOperand().getFullyConverted()) and + rhs = globalValueNumber(op.getRightOperand().getFullyConverted()) and + opname = op.getOperator() +} + +private predicate analyzableUnaryOp(UnaryOperation op) { + not op instanceof PointerDereferenceExpr and + op.isPure() and + strictcount(op.getOperand().getFullyConverted()) = 1 and + strictcount(op.getOperator()) = 1 and + not analyzableConst(op) +} + +private predicate mk_UnaryOp(GVN child, string opname, UnaryOperation op) { + analyzableUnaryOp(op) and + child = globalValueNumber(op.getOperand().getFullyConverted()) and + opname = op.getOperator() +} + +private predicate analyzableThisExpr(ThisExpr thisExpr) { + strictcount(thisExpr.getEnclosingFunction()) = 1 and + not analyzableConst(thisExpr) +} + +private predicate mk_ThisExpr(Function fcn, ThisExpr thisExpr) { + analyzableThisExpr(thisExpr) and + fcn = thisExpr.getEnclosingFunction() +} + +private predicate analyzableArrayAccess(ArrayExpr ae) { + strictcount(ae.getArrayBase().getFullyConverted()) = 1 and + strictcount(ae.getArrayOffset().getFullyConverted()) = 1 and + strictcount(mostRecentSideEffect(ae)) = 1 and + not analyzableConst(ae) +} + +private predicate mk_ArrayAccess(GVN base, GVN offset, ControlFlowNode dominator, ArrayExpr ae) { + analyzableArrayAccess(ae) and + base = globalValueNumber(ae.getArrayBase().getFullyConverted()) and + offset = globalValueNumber(ae.getArrayOffset().getFullyConverted()) and + dominator = mostRecentSideEffect(ae) +} + +private predicate analyzablePointerDereferenceExpr(PointerDereferenceExpr deref) { + strictcount(deref.getOperand().getFullyConverted()) = 1 and + strictcount(mostRecentSideEffect(deref)) = 1 and + not analyzableConst(deref) +} + +private predicate mk_Deref(GVN p, ControlFlowNode dominator, PointerDereferenceExpr deref) { + analyzablePointerDereferenceExpr(deref) and + p = globalValueNumber(deref.getOperand().getFullyConverted()) and + dominator = mostRecentSideEffect(deref) +} + +/** Gets the global value number of expression `e`. */ +cached +GVN globalValueNumber(Expr e) { + exists(int val, Type t | + mk_IntConst(val, t, e) and + result = GVN_IntConst(val, t) + ) + or + exists(float val, Type t | + mk_FloatConst(val, t, e) and + result = GVN_FloatConst(val, t) + ) + or + // Local variable with a defining value. + exists(StackVariable x, SsaDefinition def | + analyzableStackVariable(e) and + e = def.getAUse(x) and + result = globalValueNumber(def.getDefiningValue(x).getFullyConverted()) + ) + or + // Local variable without a defining value. + exists(StackVariable x, SsaDefinition def | + mk_UndefinedStackVariable(x, def, e) and + result = GVN_UndefinedStackVariable(x, def) + ) + or + // Variable with no SSA information. + exists(Variable x, ControlFlowNode dominator | + mk_OtherVariable(x, dominator, e) and + result = GVN_OtherVariable(x, dominator) + ) + or + exists(GVN qualifier, Field target | + mk_DotFieldAccess(qualifier, target, e) and + result = GVN_FieldAccess(qualifier, target) + ) + or + exists(GVN qualifier, Field target | + mk_PointerFieldAccess_with_deref(qualifier, target, e) and + result = GVN_FieldAccess(qualifier, target) + ) + or + exists(GVN qualifier, Field target | + mk_ImplicitThisFieldAccess_with_deref(qualifier, target, e) and + result = GVN_FieldAccess(qualifier, target) + ) + or + exists(Function fcn | + mk_ThisExpr(fcn, e) and + result = GVN_ThisExpr(fcn) + ) + or + exists(Type t, GVN child | + mk_Conversion(t, child, e) and + result = GVN_Conversion(t, child) + ) + or + exists(GVN lhs, GVN rhs, string opname | + mk_BinaryOp(lhs, rhs, opname, e) and + result = GVN_BinaryOp(lhs, rhs, opname) + ) + or + exists(GVN child, string opname | + mk_UnaryOp(child, opname, e) and + result = GVN_UnaryOp(child, opname) + ) + or + exists(GVN x, GVN i, ControlFlowNode dominator | + mk_ArrayAccess(x, i, dominator, e) and + result = GVN_ArrayAccess(x, i, dominator) + ) + or + exists(GVN p, ControlFlowNode dominator | + mk_Deref(p, dominator, e) and + result = GVN_Deref(p, dominator) + ) + or + not analyzableExpr(e) and result = GVN_Unanalyzable(e) +} + +private predicate analyzableConst(Expr e) { + analyzableIntConst(e) or + analyzableFloatConst(e) +} + +/** + * Holds if the expression is explicitly handled by `globalValueNumber`. + * Unanalyzable expressions still need to be given a global value number, + * but it will be a unique number that is not shared with any other + * expression. + */ +private predicate analyzableExpr(Expr e) { + analyzableConst(e) or + analyzableStackVariable(e) or + analyzableDotFieldAccess(e) or + analyzablePointerFieldAccess(e) or + analyzableImplicitThisFieldAccess(e) or + analyzableOtherVariable(e) or + analyzableConversion(e) or + analyzableBinaryOp(e) or + analyzableUnaryOp(e) or + analyzableThisExpr(e) or + analyzableArrayAccess(e) or + analyzablePointerDereferenceExpr(e) +} From 8aae2990d0ee69d5b30c7dd8c9c212996ec6b448 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 3 Feb 2020 16:15:49 +0100 Subject: [PATCH 128/148] C++: Formatting --- .../src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll index 15e89a15390..7a2d43a26e0 100644 --- a/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll +++ b/cpp/ql/src/semmle/code/cpp/valuenumbering/GlobalValueNumbering.qll @@ -1 +1 @@ -import GlobalValueNumberingImpl \ No newline at end of file +import GlobalValueNumberingImpl From bbd60f52ba86bda1c6d3221c879a85bff4fd4cc8 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Fri, 31 Jan 2020 22:52:30 +0100 Subject: [PATCH 129/148] JS: add additional flow steps to js/path-injection --- change-notes/1.24/analysis-javascript.md | 1 + .../security/dataflow/TaintedPath.qll | 34 + .../CWE-022/TaintedPath/Consistency.expected | 5 + .../CWE-022/TaintedPath/TaintedPath.expected | 951 ++++++++++++++++++ .../TaintedPath/tainted-string-steps.js | 30 + 5 files changed, 1021 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/tainted-string-steps.js diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index 1405a26d186..254819b56df 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -39,6 +39,7 @@ | Expression has no effect (`js/useless-expression`) | Fewer false positive results | The query now recognizes block-level flow type annotations and ignores the first statement of a try block. | | Use of call stack introspection in strict mode (`js/strict-mode-call-stack-introspection`) | Fewer false positive results | The query no longer flags expression statements. | | Missing CSRF middleware (`js/missing-token-validation`) | Fewer false positive results | The query reports fewer duplicates and only flags handlers that explicitly access cookie data. | +| Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional ways dangerous paths can be constructed. | ## Changes to libraries diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll index b9a3b234db9..3c6a1415ee0 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll @@ -67,6 +67,40 @@ module TaintedPath { read.getPropertyName() != "length" and srclabel = dstlabel ) + or + // string method calls of interest + exists(DataFlow::MethodCallNode mcn | srclabel = dstlabel | + exists(string substringMethodName | + substringMethodName = "substr" or + substringMethodName = "substring" or + substringMethodName = "slice" + | + mcn.calls(src, substringMethodName) and + // to avoid very dynamic transformations, require at least one fixed index + exists(mcn.getAnArgument().asExpr().getIntValue()) and + dst = mcn + ) or + exists(string argumentlessMethodName | + argumentlessMethodName = "toLocaleLowerCase" or + argumentlessMethodName = "toLocaleUpperCase" or + argumentlessMethodName = "toLowerCase" or + argumentlessMethodName = "toUpperCase" or + argumentlessMethodName = "trim" or + argumentlessMethodName = "trimLeft" or + argumentlessMethodName = "trimRight" + | + mcn.calls(src, argumentlessMethodName) and + dst = mcn + ) + or + mcn.calls(src, "split") and + dst = mcn and + not exists (DataFlow::Node splitBy | + splitBy = mcn.getArgument(0)| + splitBy.mayHaveStringValue("/") or + any(DataFlow::RegExpLiteralNode reg | reg.getRoot().getAMatchedString() = "/").flowsTo(splitBy) + ) + ) } /** diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected index 187526de3a2..659b85c8b14 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected @@ -1 +1,6 @@ | normalizedPaths.js:208:38:208:63 | // OK - ... anyway | Spurious alert | +| tainted-string-steps.js:13:41:13:72 | // NOT ... flagged | Missing alert | +| tainted-string-steps.js:14:41:14:72 | // NOT ... flagged | Missing alert | +| tainted-string-steps.js:15:50:15:81 | // NOT ... flagged | Missing alert | +| tainted-string-steps.js:25:43:25:74 | // NOT ... flagged | Missing alert | +| tainted-string-steps.js:26:49:26:74 | // OK - ... flagged | Spurious alert | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected index 056f020660c..0931e6eb936 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected @@ -1183,6 +1183,404 @@ nodes | tainted-sendFile.js:25:34:25:45 | req.params.x | | tainted-sendFile.js:25:34:25:45 | req.params.x | | tainted-sendFile.js:25:34:25:45 | req.params.x | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:24:6:30 | req.url | +| tainted-string-steps.js:6:24:6:30 | req.url | +| tainted-string-steps.js:6:24:6:30 | req.url | +| tainted-string-steps.js:6:24:6:30 | req.url | +| tainted-string-steps.js:6:24:6:30 | req.url | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | | torrents.js:5:6:5:38 | name | | torrents.js:5:6:5:38 | name | | torrents.js:5:6:5:38 | name | @@ -2930,6 +3328,550 @@ edges | tainted-sendFile.js:25:34:25:45 | req.params.x | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | | tainted-sendFile.js:25:34:25:45 | req.params.x | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | | tainted-sendFile.js:25:34:25:45 | req.params.x | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:8:18:8:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:9:18:9:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:10:18:10:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:11:18:11:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:17:18:17:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:18:18:18:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:24:18:24:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:26:18:26:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:7:6:48 | path | tainted-string-steps.js:27:18:27:21 | path | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:37 | url.par ... , true) | tainted-string-steps.js:6:14:6:43 | url.par ... ).query | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:43 | url.par ... ).query | tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:14:6:48 | url.par ... ry.path | tainted-string-steps.js:6:7:6:48 | path | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:6:14:6:37 | url.par ... , true) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:8:18:8:21 | path | tainted-string-steps.js:8:18:8:34 | path.substring(4) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:9:18:9:21 | path | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:10:18:10:21 | path | tainted-string-steps.js:10:18:10:31 | path.substr(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:11:18:11:21 | path | tainted-string-steps.js:11:18:11:30 | path.slice(4) | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:17:18:17:21 | path | tainted-string-steps.js:17:18:17:28 | path.trim() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:18:18:18:21 | path | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:21 | path | tainted-string-steps.js:24:18:24:32 | path.split("?") | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:24:18:24:32 | path.split("?") | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:21 | path | tainted-string-steps.js:26:18:26:36 | path.split(unknown) | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:26:18:26:36 | path.split(unknown) | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | +| tainted-string-steps.js:27:18:27:21 | path | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | | torrents.js:5:6:5:38 | name | torrents.js:6:24:6:27 | name | | torrents.js:5:6:5:38 | name | torrents.js:6:24:6:27 | name | | torrents.js:5:6:5:38 | name | torrents.js:6:24:6:27 | name | @@ -3022,5 +3964,14 @@ edges | tainted-sendFile.js:18:43:18:58 | req.param("dir") | tainted-sendFile.js:18:43:18:58 | req.param("dir") | tainted-sendFile.js:18:43:18:58 | req.param("dir") | This path depends on $@. | tainted-sendFile.js:18:43:18:58 | req.param("dir") | a user-provided value | | tainted-sendFile.js:24:16:24:49 | path.re ... rams.x) | tainted-sendFile.js:24:37:24:48 | req.params.x | tainted-sendFile.js:24:16:24:49 | path.re ... rams.x) | This path depends on $@. | tainted-sendFile.js:24:37:24:48 | req.params.x | a user-provided value | | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | tainted-sendFile.js:25:34:25:45 | req.params.x | tainted-sendFile.js:25:16:25:46 | path.jo ... rams.x) | This path depends on $@. | tainted-sendFile.js:25:34:25:45 | req.params.x | a user-provided value | +| tainted-string-steps.js:8:18:8:34 | path.substring(4) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:8:18:8:34 | path.substring(4) | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:9:18:9:37 | path.substring(0, i) | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:10:18:10:31 | path.substr(4) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:10:18:10:31 | path.substr(4) | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:11:18:11:30 | path.slice(4) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:11:18:11:30 | path.slice(4) | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:17:18:17:28 | path.trim() | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:17:18:17:28 | path.trim() | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:18:18:18:35 | path.toLowerCase() | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:24:18:24:35 | path.split("?")[0] | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:26:18:26:45 | path.sp ... hatever | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | +| tainted-string-steps.js:27:18:27:36 | path.split(unknown) | tainted-string-steps.js:6:24:6:30 | req.url | tainted-string-steps.js:27:18:27:36 | path.split(unknown) | This path depends on $@. | tainted-string-steps.js:6:24:6:30 | req.url | a user-provided value | | torrents.js:7:25:7:27 | loc | torrents.js:5:13:5:38 | parseTo ... t).name | torrents.js:7:25:7:27 | loc | This path depends on $@. | torrents.js:5:13:5:38 | parseTo ... t).name | a user-provided value | | views.js:1:43:1:55 | req.params[0] | views.js:1:43:1:55 | req.params[0] | views.js:1:43:1:55 | req.params[0] | This path depends on $@. | views.js:1:43:1:55 | req.params[0] | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/tainted-string-steps.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/tainted-string-steps.js new file mode 100644 index 00000000000..6651463ee52 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/tainted-string-steps.js @@ -0,0 +1,30 @@ +var fs = require('fs'), + http = require('http'), + url = require('url'); + +var server = http.createServer(function(req, res) { + let path = url.parse(req.url, true).query.path; + fs.readFileSync(path.substring(i, j)); // OK + fs.readFileSync(path.substring(4)); // NOT OK + fs.readFileSync(path.substring(0, i)); // NOT OK + fs.readFileSync(path.substr(4)); // NOT OK + fs.readFileSync(path.slice(4)); // NOT OK + + fs.readFileSync(path.concat(unknown)); // NOT OK -- but not yet flagged + fs.readFileSync(unknown.concat(path)); // NOT OK -- but not yet flagged + fs.readFileSync(unknown.concat(unknown, path)); // NOT OK -- but not yet flagged + + fs.readFileSync(path.trim()); // NOT OK + fs.readFileSync(path.toLowerCase()); // NOT OK + + fs.readFileSync(path.split('/')); // OK -- for now + fs.readFileSync(path.split('/')[0]); // OK -- for now + fs.readFileSync(path.split('/')[i]); // OK -- for now + fs.readFileSync(path.split(/\//)[i]); // OK -- for now + fs.readFileSync(path.split("?")[0]); // NOT OK + fs.readFileSync(path.split(unknown)[i]); // NOT OK -- but not yet flagged + fs.readFileSync(path.split(unknown).whatever); // OK -- but still flagged + fs.readFileSync(path.split(unknown)); // NOT OK +}); + +server.listen(); From b4385c6e605031c5ee615cbad24413fa9abdc813 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Tue, 4 Feb 2020 08:40:36 +0100 Subject: [PATCH 130/148] C++: Don't use GVN in AST DataFlow BarrierNode It turns out that the evaluator will evaluate the GVN stage even when no predicate from it is needed after optimization of the subsequent stages. The GVN library is expensive to evaluate, and it'll become even more expensive when we switch its implementation to IR. This PR disables the use of GVN in `DataFlow::BarrierNode` for the AST data-flow library, which should improve performance when evaluating a single data-flow query on a snapshot with no cache. Precision decreases slightly, leading to a new FP in the qltests. There is no corresponding change for the IR data-flow library since IR GVN is not very expensive. --- .../src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll | 7 +++---- .../library-tests/dataflow/dataflow-tests/BarrierGuard.cpp | 2 +- .../library-tests/dataflow/dataflow-tests/test.expected | 1 + .../dataflow/dataflow-tests/test_diff.expected | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll index 2c3438a1e46..700087871cc 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowUtil.qll @@ -6,7 +6,6 @@ private import cpp private import semmle.code.cpp.dataflow.internal.FlowVar private import semmle.code.cpp.models.interfaces.DataFlow private import semmle.code.cpp.controlflow.Guards -private import semmle.code.cpp.valuenumbering.GlobalValueNumbering cached private newtype TNode = @@ -689,9 +688,9 @@ class BarrierGuard extends GuardCondition { /** Gets a node guarded by this guard. */ final ExprNode getAGuardedNode() { - exists(GVN value, boolean branch | - result.getExpr() = value.getAnExpr() and - this.checks(value.getAnExpr(), branch) and + exists(SsaDefinition def, Variable v, boolean branch | + result.getExpr() = def.getAUse(v) and + this.checks(def.getAUse(v), branch) and this.controls(result.getExpr().getBasicBlock(), branch) ) } diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp b/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp index f2b91c00ed0..1896a066f68 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/BarrierGuard.cpp @@ -48,7 +48,7 @@ struct XY { void bg_stackstruct(XY s1, XY s2) { s1.x = source(); if (guarded(s1.x)) { - sink(s1.x); // no flow + sink(s1.x); // no flow [FALSE POSITIVE in AST] } else if (guarded(s1.y)) { sink(s1.x); // flow } else if (guarded(s2.y)) { diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected index 24fa6fdb5bd..04ad48cd4d6 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected @@ -3,6 +3,7 @@ | BarrierGuard.cpp:25:10:25:15 | source | BarrierGuard.cpp:21:17:21:22 | source | | BarrierGuard.cpp:31:10:31:15 | source | BarrierGuard.cpp:29:16:29:21 | source | | BarrierGuard.cpp:33:10:33:15 | source | BarrierGuard.cpp:29:16:29:21 | source | +| BarrierGuard.cpp:51:13:51:13 | x | BarrierGuard.cpp:49:10:49:15 | call to source | | BarrierGuard.cpp:53:13:53:13 | x | BarrierGuard.cpp:49:10:49:15 | call to source | | BarrierGuard.cpp:55:13:55:13 | x | BarrierGuard.cpp:49:10:49:15 | call to source | | BarrierGuard.cpp:62:14:62:14 | x | BarrierGuard.cpp:60:11:60:16 | call to source | diff --git a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected index 31639764ad6..8daa9b4b39b 100644 --- a/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected @@ -1,3 +1,4 @@ +| BarrierGuard.cpp:49:10:49:15 | BarrierGuard.cpp:51:13:51:13 | AST only | | BarrierGuard.cpp:60:11:60:16 | BarrierGuard.cpp:62:14:62:14 | AST only | | clang.cpp:12:9:12:20 | clang.cpp:22:8:22:20 | AST only | | clang.cpp:28:27:28:32 | clang.cpp:30:27:30:34 | AST only | From e21c24c60e269c51db3a5bad5d7cdbb6c1eaab2b Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Tue, 4 Feb 2020 09:38:55 +0000 Subject: [PATCH 131/148] JavaScript: Add failing test case. --- javascript/ql/test/library-tests/NPM/Modules.expected | 1 + .../test/library-tests/NPM/NPMPackage_getMainModule.expected | 1 + javascript/ql/test/library-tests/NPM/PackageJSON.expected | 1 + .../ql/test/library-tests/NPM/src/node_modules/d/main.js | 1 + .../ql/test/library-tests/NPM/src/node_modules/d/package.json | 4 ++++ 5 files changed, 8 insertions(+) create mode 100644 javascript/ql/test/library-tests/NPM/src/node_modules/d/main.js create mode 100644 javascript/ql/test/library-tests/NPM/src/node_modules/d/package.json diff --git a/javascript/ql/test/library-tests/NPM/Modules.expected b/javascript/ql/test/library-tests/NPM/Modules.expected index e7fa54d6f9a..9ce1e5fccd6 100644 --- a/javascript/ql/test/library-tests/NPM/Modules.expected +++ b/javascript/ql/test/library-tests/NPM/Modules.expected @@ -1,6 +1,7 @@ | b | src/node_modules/b/lib/index.js:1:1:2:0 | | | b | src/node_modules/b/lib/index.ts:1:1:2:0 | | | c | src/node_modules/c/src/index.js:1:1:2:0 | | +| d | src/node_modules/d/main.js:1:1:2:0 | | | test-package | src/index.js:1:1:4:0 | | | test-package | src/lib/tst2.js:1:1:1:14 | | | test-package | src/lib/tst.js:1:1:4:0 | | diff --git a/javascript/ql/test/library-tests/NPM/NPMPackage_getMainModule.expected b/javascript/ql/test/library-tests/NPM/NPMPackage_getMainModule.expected index a8b7753abae..c8fe4a8ba4b 100644 --- a/javascript/ql/test/library-tests/NPM/NPMPackage_getMainModule.expected +++ b/javascript/ql/test/library-tests/NPM/NPMPackage_getMainModule.expected @@ -1,4 +1,5 @@ | b | src/node_modules/b/lib/index.ts:1:1:2:0 | | | c | src/node_modules/c/src/index.js:1:1:2:0 | | +| d | src/node_modules/d/main.js:1:1:2:0 | | | test-package | src/index.js:1:1:4:0 | | | third-party-module | src/node_modules/third-party-module/fancy.js:1:1:4:0 | | diff --git a/javascript/ql/test/library-tests/NPM/PackageJSON.expected b/javascript/ql/test/library-tests/NPM/PackageJSON.expected index 22e42834813..5ad71e3cb45 100644 --- a/javascript/ql/test/library-tests/NPM/PackageJSON.expected +++ b/javascript/ql/test/library-tests/NPM/PackageJSON.expected @@ -1,4 +1,5 @@ | src/node_modules/b/package.json:1:1:4:1 | {\\n "na ... "lib"\\n} | | src/node_modules/c/package.json:1:1:4:1 | {\\n "na ... src/"\\n} | +| src/node_modules/d/package.json:1:1:4:1 | {\\n "na ... main"\\n} | | src/node_modules/third-party-module/package.json:1:1:5:1 | {\\n "na ... y.js"\\n} | | src/package.json:1:1:18:1 | {\\n "na ... "\\n }\\n} | diff --git a/javascript/ql/test/library-tests/NPM/src/node_modules/d/main.js b/javascript/ql/test/library-tests/NPM/src/node_modules/d/main.js new file mode 100644 index 00000000000..987d6d7e401 --- /dev/null +++ b/javascript/ql/test/library-tests/NPM/src/node_modules/d/main.js @@ -0,0 +1 @@ +export default "d"; diff --git a/javascript/ql/test/library-tests/NPM/src/node_modules/d/package.json b/javascript/ql/test/library-tests/NPM/src/node_modules/d/package.json new file mode 100644 index 00000000000..2b264e942ef --- /dev/null +++ b/javascript/ql/test/library-tests/NPM/src/node_modules/d/package.json @@ -0,0 +1,4 @@ +{ + "name": "d", + "main": "main" +} From 43e4ed1e18e886be12da7abd7a96f30820a304ab Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Sat, 1 Feb 2020 17:50:15 +0000 Subject: [PATCH 132/148] JavaScript: Teach `resolveMainModule` to try adding extensions. --- .../semmle/javascript/NodeModuleResolutionImpl.qll | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll b/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll index 5d85d47e1d2..83023fabb6f 100644 --- a/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll +++ b/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll @@ -84,10 +84,16 @@ File tryExtensions(Folder dir, string basename, int priority) { File resolveMainModule(PackageJSON pkg, int priority) { if exists(MainModulePath::of(pkg)) then - exists(Container c | c = MainModulePath::of(pkg).resolve() | - result = c and priority = 0 + exists(PathExpr main | main = MainModulePath::of(pkg) | + result = main.resolve() and priority = 0 or - result = tryExtensions(c, "index", priority) + result = tryExtensions(main.resolve(), "index", priority) + or + not exists(main.resolve()) and + not exists(main.getExtension()) and + exists(int n | n = main.getNumComponent() | + result = tryExtensions(main.resolveUpTo(n-1), main.getComponent(n-1), priority) + ) ) else result = tryExtensions(pkg.getFile().getParentContainer(), "index", priority) } From 8a2c81b41c0d3fe33974e2a87127f74977bfa1f5 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 4 Feb 2020 10:49:23 +0100 Subject: [PATCH 133/148] JS: address review comments about duplicated logic --- .../security/dataflow/TaintedPath.qll | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll index 3c6a1415ee0..de0fdbc9a71 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll @@ -69,17 +69,19 @@ module TaintedPath { ) or // string method calls of interest - exists(DataFlow::MethodCallNode mcn | srclabel = dstlabel | + exists(DataFlow::MethodCallNode mcn, string name | + srclabel = dstlabel and dst = mcn and mcn.calls(src, name) + | exists(string substringMethodName | substringMethodName = "substr" or substringMethodName = "substring" or substringMethodName = "slice" | - mcn.calls(src, substringMethodName) and + name = substringMethodName and // to avoid very dynamic transformations, require at least one fixed index - exists(mcn.getAnArgument().asExpr().getIntValue()) and - dst = mcn - ) or + exists(mcn.getAnArgument().asExpr().getIntValue()) + ) + or exists(string argumentlessMethodName | argumentlessMethodName = "toLocaleLowerCase" or argumentlessMethodName = "toLocaleUpperCase" or @@ -89,16 +91,14 @@ module TaintedPath { argumentlessMethodName = "trimLeft" or argumentlessMethodName = "trimRight" | - mcn.calls(src, argumentlessMethodName) and - dst = mcn + name = argumentlessMethodName ) or - mcn.calls(src, "split") and - dst = mcn and - not exists (DataFlow::Node splitBy | - splitBy = mcn.getArgument(0)| + name = "split" and + not exists(DataFlow::Node splitBy | splitBy = mcn.getArgument(0) | splitBy.mayHaveStringValue("/") or - any(DataFlow::RegExpLiteralNode reg | reg.getRoot().getAMatchedString() = "/").flowsTo(splitBy) + any(DataFlow::RegExpLiteralNode reg | reg.getRoot().getAMatchedString() = "/") + .flowsTo(splitBy) ) ) } From bf2c944b4fec66fe11171d8d1ce22b04611c317d Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 4 Feb 2020 10:20:37 +0000 Subject: [PATCH 134/148] JS: Model concat() calls as string concatenation --- .../semmle/javascript/StringConcatenation.qll | 11 +++++++++++ .../ClassContainsTwo.expected | 4 ++++ .../StringConcatenation/ContainsTwo.expected | 4 ++++ .../StringConcatenation/StringOps.expected | 16 ++++++++++++++++ .../library-tests/StringConcatenation/tst.js | 6 ++++++ 5 files changed, 41 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/StringConcatenation.qll b/javascript/ql/src/semmle/javascript/StringConcatenation.qll index 2f423202bcb..528085f50bf 100644 --- a/javascript/ql/src/semmle/javascript/StringConcatenation.qll +++ b/javascript/ql/src/semmle/javascript/StringConcatenation.qll @@ -51,6 +51,17 @@ module StringConcatenation { call = Closure::moduleImport("goog.string.buildString").getACall() and result = call.getArgument(n) ) + or + exists(DataFlow::MethodCallNode call | + node = call and + call.getMethodName() = "concat" and + ( + n = 0 and + result = call.getReceiver() + or + result = call.getArgument(n - 1) + ) + ) } /** Gets an operand to the string concatenation defining `node`. */ diff --git a/javascript/ql/test/library-tests/StringConcatenation/ClassContainsTwo.expected b/javascript/ql/test/library-tests/StringConcatenation/ClassContainsTwo.expected index ffdaf780711..db21cc79dd2 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/ClassContainsTwo.expected +++ b/javascript/ql/test/library-tests/StringConcatenation/ClassContainsTwo.expected @@ -42,3 +42,7 @@ | tst.js:89:3:89:3 | x | | tst.js:89:3:89:14 | x += 'three' | | tst.js:90:10:90:10 | x | +| tst.js:95:3:95:30 | x = x.c ... three') | +| tst.js:95:7:95:30 | x.conca ... three') | +| tst.js:95:16:95:20 | 'two' | +| tst.js:96:10:96:10 | x | diff --git a/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected b/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected index ffdaf780711..db21cc79dd2 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected +++ b/javascript/ql/test/library-tests/StringConcatenation/ContainsTwo.expected @@ -42,3 +42,7 @@ | tst.js:89:3:89:3 | x | | tst.js:89:3:89:14 | x += 'three' | | tst.js:90:10:90:10 | x | +| tst.js:95:3:95:30 | x = x.c ... three') | +| tst.js:95:7:95:30 | x.conca ... three') | +| tst.js:95:16:95:20 | 'two' | +| tst.js:96:10:96:10 | x | diff --git a/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected b/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected index 695957924d4..0afbad59a08 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected +++ b/javascript/ql/test/library-tests/StringConcatenation/StringOps.expected @@ -45,6 +45,7 @@ concatenation | tst.js:87:5:87:14 | x += 'two' | | tst.js:89:3:89:14 | x | | tst.js:89:3:89:14 | x += 'three' | +| tst.js:95:7:95:30 | x.conca ... three') | concatenationOperand | closure.js:5:1:5:37 | build(' ... 'four') | | closure.js:5:7:5:11 | 'one' | @@ -123,6 +124,9 @@ concatenationOperand | tst.js:87:10:87:14 | 'two' | | tst.js:89:3:89:3 | x | | tst.js:89:8:89:14 | 'three' | +| tst.js:95:7:95:7 | x | +| tst.js:95:16:95:20 | 'two' | +| tst.js:95:23:95:29 | 'three' | concatenationLeaf | closure.js:5:7:5:11 | 'one' | | closure.js:5:14:5:18 | 'two' | @@ -192,6 +196,9 @@ concatenationLeaf | tst.js:87:10:87:14 | 'two' | | tst.js:89:3:89:3 | x | | tst.js:89:8:89:14 | 'three' | +| tst.js:95:7:95:7 | x | +| tst.js:95:16:95:20 | 'two' | +| tst.js:95:23:95:29 | 'three' | concatenationNode | closure.js:5:1:5:37 | build(' ... 'four') | | closure.js:5:1:5:46 | build(' ... 'five' | @@ -307,6 +314,10 @@ concatenationNode | tst.js:89:3:89:14 | x | | tst.js:89:3:89:14 | x += 'three' | | tst.js:89:8:89:14 | 'three' | +| tst.js:95:7:95:7 | x | +| tst.js:95:7:95:30 | x.conca ... three') | +| tst.js:95:16:95:20 | 'two' | +| tst.js:95:23:95:29 | 'three' | operand | closure.js:5:1:5:37 | build(' ... 'four') | 0 | closure.js:5:7:5:11 | 'one' | | closure.js:5:1:5:37 | build(' ... 'four') | 1 | closure.js:5:14:5:28 | 'two' + 'three' | @@ -407,6 +418,9 @@ operand | tst.js:89:3:89:14 | x | 1 | tst.js:89:8:89:14 | 'three' | | tst.js:89:3:89:14 | x += 'three' | 0 | tst.js:89:3:89:3 | x | | tst.js:89:3:89:14 | x += 'three' | 1 | tst.js:89:8:89:14 | 'three' | +| tst.js:95:7:95:30 | x.conca ... three') | 0 | tst.js:95:7:95:7 | x | +| tst.js:95:7:95:30 | x.conca ... three') | 1 | tst.js:95:16:95:20 | 'two' | +| tst.js:95:7:95:30 | x.conca ... three') | 2 | tst.js:95:23:95:29 | 'three' | nextLeaf | closure.js:5:7:5:11 | 'one' | closure.js:5:14:5:18 | 'two' | | closure.js:5:14:5:18 | 'two' | closure.js:5:22:5:28 | 'three' | @@ -450,6 +464,8 @@ nextLeaf | tst.js:61:27:61:27 | x | tst.js:61:29:61:33 | last | | tst.js:87:5:87:5 | x | tst.js:87:10:87:14 | 'two' | | tst.js:89:3:89:3 | x | tst.js:89:8:89:14 | 'three' | +| tst.js:95:7:95:7 | x | tst.js:95:16:95:20 | 'two' | +| tst.js:95:16:95:20 | 'two' | tst.js:95:23:95:29 | 'three' | htmlRoot | html-concat.js:2:14:2:26 | `${x}` | | html-concat.js:3:14:3:26 | `${x}` | diff --git a/javascript/ql/test/library-tests/StringConcatenation/tst.js b/javascript/ql/test/library-tests/StringConcatenation/tst.js index e3f945a6fdd..7aac7010f00 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/tst.js +++ b/javascript/ql/test/library-tests/StringConcatenation/tst.js @@ -89,3 +89,9 @@ function addExprPhi(b) { x += 'three'; return x; } + +function concatCall() { + let x = 'one'; + x = x.concat('two', 'three'); + return x; +} From 15e26666cdd8c1482c1d19bb60d1bab4d9e4681a Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 4 Feb 2020 12:05:26 +0100 Subject: [PATCH 135/148] add declaration for private field in syntax error test --- .../LanguageFeatures/SyntaxError/SyntaxError.expected | 2 +- .../LanguageFeatures/SyntaxError/destructingPrivate.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected index af30b4bbb35..e643a123124 100644 --- a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected +++ b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/SyntaxError.expected @@ -1,4 +1,4 @@ | arrows.js:1:5:1:5 | Error: Argument name clash | Error: Argument name clash | -| destructingPrivate.js:3:6:3:6 | Error: Unexpected token | Error: Unexpected token | +| destructingPrivate.js:2:12:2:12 | Error: Unexpected token | Error: Unexpected token | | privateMethod.js:2:3:2:3 | Error: Only fields, not methods, can be declared private. | Error: Only fields, not methods, can be declared private. | | tst.js:2:12:2:12 | Error: Unterminated string constant | Error: Unterminated string constant | diff --git a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/destructingPrivate.js b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/destructingPrivate.js index 45427b4203e..060111d86b4 100644 --- a/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/destructingPrivate.js +++ b/javascript/ql/test/query-tests/LanguageFeatures/SyntaxError/destructingPrivate.js @@ -1,4 +1,5 @@ class C { + #privDecl; bar() { {#privDecl} = this; } From c185cededfaa727cf7b86ec37ed51203c69597a1 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Wed, 29 Jan 2020 14:47:25 +0000 Subject: [PATCH 136/148] JS: More pruning and more data flow --- .../CWE-400/PrototypePollutionUtility.ql | 82 ++- .../PrototypePollutionUtility.expected | 622 ++++++++++++------ .../PrototypePollutionUtility/tests.js | 78 +++ 3 files changed, 581 insertions(+), 201 deletions(-) diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index d55a40422a1..5275b487f9c 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -15,6 +15,7 @@ import javascript import DataFlow import PathGraph import semmle.javascript.dataflow.InferredTypes +import semmle.javascript.dataflow.internal.FlowSteps /** * Gets a node that refers to an element of `array`, likely obtained @@ -52,9 +53,12 @@ abstract class EnumeratedPropName extends DataFlow::Node { * * For example, gets `src[key]` in `for (var key in src) { src[key]; }`. */ - PropRead getASourceProp() { - result = AccessPath::getAnAliasedSourceNode(getSourceObject()).getAPropertyRead() and - result.getPropertyNameExpr().flow().getImmediatePredecessor*() = this + SourceNode getASourceProp() { + exists(Node base, Node key | + dynamicPropReadStep(base, key, result) and + AccessPath::getAnAliasedSourceNode(getSourceObject()).flowsTo(base) and + key.getImmediatePredecessor*() = this + ) } } @@ -102,7 +106,7 @@ class EntriesEnumeratedPropName extends EnumeratedPropName { result = entries.getArgument(0) } - override PropRead getASourceProp() { + override SourceNode getASourceProp() { result = super.getASourceProp() or result = entry.getAPropertyRead("1") @@ -133,6 +137,9 @@ class DynamicPropRead extends DataFlow::SourceNode, DataFlow::ValueNode { /** Gets the base of the dynamic read. */ DataFlow::Node getBase() { result = astNode.getBase().flow() } + /** Gets the node holding the name of the property. */ + DataFlow::Node getPropertyNameNode() { result = astNode.getIndex().flow() } + /** * Holds if the value of this read was assigned to earlier in the same basic block. * @@ -154,6 +161,72 @@ class DynamicPropRead extends DataFlow::SourceNode, DataFlow::ValueNode { } } +/** + * Holds if `output` is the result of `base[key]`, either directly or through + * one or more function calls. + */ +predicate dynamicPropReadStep(Node base, Node key, SourceNode output) { + exists(DynamicPropRead read | + not read.hasDominatingAssignment() and + base = read.getBase() and + key = read.getPropertyNameNode() and + output = read + ) + or + // Summarize functions returning a dynamic property read of two parameters. + exists(CallNode call, Function callee, ParameterNode baseParam, ParameterNode keyParam, Node innerBase, Node innerKey, SourceNode innerOutput | + dynamicPropReadStep(innerBase, innerKey, innerOutput) and + baseParam.flowsTo(innerBase) and + keyParam.flowsTo(innerKey) and + innerOutput.flowsTo(callee.getAReturnedExpr().flow()) and + call.getACallee() = callee and + argumentPassing(call, base, callee, baseParam) and + argumentPassing(call, key, callee, keyParam) and + output = call + ) +} + +/** + * Holds if `node` may flow from an enumerated prop name, possibly + * into function calls (but not returns). + */ +predicate isEnumeratedPropName(Node node) { + node instanceof EnumeratedPropName + or + exists(Node pred | + isEnumeratedPropName(pred) + | + node = pred.getASuccessor() + or + argumentPassing(_, pred, _, node) + or + // Handle one level of callbacks + exists(FunctionNode function, ParameterNode callback, int i | + pred = callback.getAnInvocation().getArgument(i) and + argumentPassing(_, function, _, callback) and + node = function.getParameter(i) + ) + ) +} + +/** + * Holds if `node` may refer to `Object.prototype` obtained through dynamic property + * read of a property obtained through property enumeration. + */ +predicate isPotentiallyObjectPrototype(SourceNode node) { + exists(Node base, Node key | + dynamicPropReadStep(base, key, node) and + isEnumeratedPropName(key) and + not arePropertiesEnumerated(base.getALocalSource()) // ignore `for (let key in src) { ... src[key] ... }` + ) + or + exists(Node use | + isPotentiallyObjectPrototype(use.getALocalSource()) + | + argumentPassing(_, use, _, node) + ) +} + /** * Holds if there is a dynamic property assignment of form `base[prop] = rhs` * which might act as the writing operation in a recursive merge function. @@ -168,6 +241,7 @@ predicate dynamicPropWrite(DataFlow::Node base, DataFlow::Node prop, DataFlow::N exists(AssignExpr write, IndexExpr index | index = write.getLhs() and base = index.getBase().flow() and + isPotentiallyObjectPrototype(base.getALocalSource()) and prop = index.getPropertyNameExpr().flow() and rhs = write.getRhs().flow() and not exists(prop.getStringValue()) and diff --git a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected index fe8fb9351d0..9d80066ebfb 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected +++ b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected @@ -302,24 +302,6 @@ nodes | PrototypePollutionUtility/tests.js:121:24:121:31 | src[key] | | PrototypePollutionUtility/tests.js:121:28:121:30 | key | | PrototypePollutionUtility/tests.js:121:28:121:30 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | -| PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | -| PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:144:16:144:18 | key | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | @@ -548,9 +530,6 @@ nodes | PrototypePollutionUtility/tests.js:213:29:213:32 | key2 | | PrototypePollutionUtility/tests.js:213:35:213:39 | value | | PrototypePollutionUtility/tests.js:213:35:213:39 | value | -| PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | -| PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | -| PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | | PrototypePollutionUtility/tests.js:217:5:217:13 | map[key1] | | PrototypePollutionUtility/tests.js:217:5:217:13 | map[key1] | | PrototypePollutionUtility/tests.js:217:5:217:13 | map[key1] | @@ -585,9 +564,6 @@ nodes | PrototypePollutionUtility/tests.js:229:32:229:35 | key2 | | PrototypePollutionUtility/tests.js:229:38:229:42 | value | | PrototypePollutionUtility/tests.js:229:38:229:42 | value | -| PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | -| PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | -| PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | @@ -616,28 +592,6 @@ nodes | PrototypePollutionUtility/tests.js:240:36:240:44 | data[key] | | PrototypePollutionUtility/tests.js:240:41:240:43 | key | | PrototypePollutionUtility/tests.js:240:41:240:43 | key | -| PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | -| PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:53 | src | -| PrototypePollutionUtility/tests.js:257:51:257:53 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:55:257:57 | key | -| PrototypePollutionUtility/tests.js:257:55:257:57 | key | | PrototypePollutionUtility/tests.js:263:27:263:29 | dst | | PrototypePollutionUtility/tests.js:263:27:263:29 | dst | | PrototypePollutionUtility/tests.js:265:13:265:26 | key | @@ -707,42 +661,6 @@ nodes | PrototypePollutionUtility/tests.js:280:24:280:31 | src[key] | | PrototypePollutionUtility/tests.js:280:28:280:30 | key | | PrototypePollutionUtility/tests.js:280:28:280:30 | key | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | -| PrototypePollutionUtility/tests.js:289:40:289:42 | src | -| PrototypePollutionUtility/tests.js:289:40:289:42 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | -| PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | -| PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | | PrototypePollutionUtility/tests.js:301:27:301:29 | dst | | PrototypePollutionUtility/tests.js:301:27:301:29 | dst | | PrototypePollutionUtility/tests.js:301:32:301:34 | src | @@ -848,6 +766,193 @@ nodes | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | | PrototypePollutionUtility/tests.js:357:38:357:40 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | +| PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | +| PrototypePollutionUtility/tests.js:375:32:375:34 | dst | +| PrototypePollutionUtility/tests.js:375:32:375:34 | dst | +| PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:36:375:38 | key | +| PrototypePollutionUtility/tests.js:375:36:375:38 | key | +| PrototypePollutionUtility/tests.js:375:42:375:44 | src | +| PrototypePollutionUtility/tests.js:375:42:375:44 | src | +| PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:375:46:375:48 | key | +| PrototypePollutionUtility/tests.js:375:46:375:48 | key | +| PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | +| PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | +| PrototypePollutionUtility/tests.js:382:35:382:37 | src | +| PrototypePollutionUtility/tests.js:382:35:382:37 | src | +| PrototypePollutionUtility/tests.js:383:17:383:19 | src | +| PrototypePollutionUtility/tests.js:383:17:383:19 | src | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:385:33:385:35 | dst | +| PrototypePollutionUtility/tests.js:385:33:385:35 | dst | +| PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:37:385:39 | key | +| PrototypePollutionUtility/tests.js:385:37:385:39 | key | +| PrototypePollutionUtility/tests.js:385:43:385:47 | value | +| PrototypePollutionUtility/tests.js:385:43:385:47 | value | +| PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:33:398:35 | src | +| PrototypePollutionUtility/tests.js:398:33:398:35 | src | +| PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:34:399:36 | dst | +| PrototypePollutionUtility/tests.js:399:34:399:36 | dst | +| PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:413:34:413:36 | dst | +| PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:36:415:38 | src | +| PrototypePollutionUtility/tests.js:415:36:415:38 | src | +| PrototypePollutionUtility/tests.js:415:41:415:43 | key | +| PrototypePollutionUtility/tests.js:416:13:416:45 | target | +| PrototypePollutionUtility/tests.js:416:13:416:45 | target | +| PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | +| PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | +| PrototypePollutionUtility/tests.js:416:37:416:39 | dst | +| PrototypePollutionUtility/tests.js:416:42:416:44 | key | +| PrototypePollutionUtility/tests.js:418:37:418:42 | target | +| PrototypePollutionUtility/tests.js:418:37:418:42 | target | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:420:13:420:15 | dst | +| PrototypePollutionUtility/tests.js:420:13:420:15 | dst | +| PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:430:33:430:35 | src | +| PrototypePollutionUtility/tests.js:430:33:430:35 | src | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:30:432:32 | src | +| PrototypePollutionUtility/tests.js:432:30:432:32 | src | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:437:24:437:28 | value | | examples/PrototypePollutionUtility.js:1:16:1:18 | dst | | examples/PrototypePollutionUtility.js:1:16:1:18 | dst | | examples/PrototypePollutionUtility.js:1:21:1:23 | src | @@ -1317,29 +1422,6 @@ edges | PrototypePollutionUtility/tests.js:121:28:121:30 | key | PrototypePollutionUtility/tests.js:121:24:121:31 | src[key] | | PrototypePollutionUtility/tests.js:121:28:121:30 | key | PrototypePollutionUtility/tests.js:121:24:121:31 | src[key] | | PrototypePollutionUtility/tests.js:121:28:121:30 | key | PrototypePollutionUtility/tests.js:121:24:121:31 | src[key] | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:13:128:15 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:127:14:127:16 | key | PrototypePollutionUtility/tests.js:128:24:128:26 | key | -| PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:128:24:128:26 | key | PrototypePollutionUtility/tests.js:128:20:128:27 | src[key] | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | -| PrototypePollutionUtility/tests.js:143:14:143:16 | key | PrototypePollutionUtility/tests.js:144:16:144:18 | key | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | PrototypePollutionUtility/tests.js:152:22:152:24 | dst | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | PrototypePollutionUtility/tests.js:152:22:152:24 | dst | | PrototypePollutionUtility/tests.js:149:31:149:33 | dst | PrototypePollutionUtility/tests.js:152:22:152:24 | dst | @@ -1615,10 +1697,6 @@ edges | PrototypePollutionUtility/tests.js:208:32:208:38 | keys[i] | PrototypePollutionUtility/tests.js:208:28:208:39 | src[keys[i]] | | PrototypePollutionUtility/tests.js:208:32:208:38 | keys[i] | PrototypePollutionUtility/tests.js:208:28:208:39 | src[keys[i]] | | PrototypePollutionUtility/tests.js:208:32:208:38 | keys[i] | PrototypePollutionUtility/tests.js:208:28:208:39 | src[keys[i]] | -| PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | -| PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | -| PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | -| PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:215:13:215:16 | key1 | | PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:217:9:217:12 | key1 | | PrototypePollutionUtility/tests.js:213:23:213:26 | key1 | PrototypePollutionUtility/tests.js:217:9:217:12 | key1 | | PrototypePollutionUtility/tests.js:213:29:213:32 | key2 | PrototypePollutionUtility/tests.js:217:15:217:18 | key2 | @@ -1665,10 +1743,6 @@ edges | PrototypePollutionUtility/tests.js:225:33:225:41 | data[key] | PrototypePollutionUtility/tests.js:213:35:213:39 | value | | PrototypePollutionUtility/tests.js:225:38:225:40 | key | PrototypePollutionUtility/tests.js:225:33:225:41 | data[key] | | PrototypePollutionUtility/tests.js:225:38:225:40 | key | PrototypePollutionUtility/tests.js:225:33:225:41 | data[key] | -| PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | -| PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | -| PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | -| PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:231:13:231:16 | key1 | | PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:233:9:233:12 | key1 | | PrototypePollutionUtility/tests.js:229:26:229:29 | key1 | PrototypePollutionUtility/tests.js:233:9:233:12 | key1 | | PrototypePollutionUtility/tests.js:229:32:229:35 | key2 | PrototypePollutionUtility/tests.js:233:15:233:18 | key2 | @@ -1715,40 +1789,6 @@ edges | PrototypePollutionUtility/tests.js:240:36:240:44 | data[key] | PrototypePollutionUtility/tests.js:229:38:229:42 | value | | PrototypePollutionUtility/tests.js:240:41:240:43 | key | PrototypePollutionUtility/tests.js:240:36:240:44 | data[key] | | PrototypePollutionUtility/tests.js:240:41:240:43 | key | PrototypePollutionUtility/tests.js:240:36:240:44 | data[key] | -| PrototypePollutionUtility/tests.js:252:29:252:31 | src | PrototypePollutionUtility/tests.js:257:51:257:53 | src | -| PrototypePollutionUtility/tests.js:252:29:252:31 | src | PrototypePollutionUtility/tests.js:257:51:257:53 | src | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:20:257:22 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:55:257:57 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:55:257:57 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:55:257:57 | key | -| PrototypePollutionUtility/tests.js:255:14:255:16 | key | PrototypePollutionUtility/tests.js:257:55:257:57 | key | -| PrototypePollutionUtility/tests.js:257:51:257:53 | src | PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:53 | src | PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:252:29:252:31 | src | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | PrototypePollutionUtility/tests.js:257:27:257:59 | mergeWi ... c[key]) | -| PrototypePollutionUtility/tests.js:257:55:257:57 | key | PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | -| PrototypePollutionUtility/tests.js:257:55:257:57 | key | PrototypePollutionUtility/tests.js:257:51:257:58 | src[key] | | PrototypePollutionUtility/tests.js:263:27:263:29 | dst | PrototypePollutionUtility/tests.js:268:30:268:32 | dst | | PrototypePollutionUtility/tests.js:263:27:263:29 | dst | PrototypePollutionUtility/tests.js:268:30:268:32 | dst | | PrototypePollutionUtility/tests.js:263:27:263:29 | dst | PrototypePollutionUtility/tests.js:270:13:270:15 | dst | @@ -1837,56 +1877,6 @@ edges | PrototypePollutionUtility/tests.js:280:28:280:30 | key | PrototypePollutionUtility/tests.js:280:24:280:31 | src[key] | | PrototypePollutionUtility/tests.js:280:28:280:30 | key | PrototypePollutionUtility/tests.js:280:24:280:31 | src[key] | | PrototypePollutionUtility/tests.js:280:28:280:30 | key | PrototypePollutionUtility/tests.js:280:24:280:31 | src[key] | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | PrototypePollutionUtility/tests.js:289:40:289:42 | src | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | PrototypePollutionUtility/tests.js:289:40:289:42 | src | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | PrototypePollutionUtility/tests.js:293:37:293:39 | src | -| PrototypePollutionUtility/tests.js:285:28:285:30 | src | PrototypePollutionUtility/tests.js:293:37:293:39 | src | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:285:33:285:36 | path | PrototypePollutionUtility/tests.js:292:24:292:27 | path | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:44:289:46 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:289:76:289:78 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:30:293:32 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:41:293:43 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:41:293:43 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:41:293:43 | key | -| PrototypePollutionUtility/tests.js:286:14:286:16 | key | PrototypePollutionUtility/tests.js:293:41:293:43 | key | -| PrototypePollutionUtility/tests.js:289:40:289:42 | src | PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:42 | src | PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | PrototypePollutionUtility/tests.js:285:28:285:30 | src | -| PrototypePollutionUtility/tests.js:289:44:289:46 | key | PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:44:289:46 | key | PrototypePollutionUtility/tests.js:289:40:289:47 | src[key] | -| PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | PrototypePollutionUtility/tests.js:285:33:285:36 | path | -| PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | PrototypePollutionUtility/tests.js:285:33:285:36 | path | -| PrototypePollutionUtility/tests.js:289:76:289:78 | key | PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | -| PrototypePollutionUtility/tests.js:289:76:289:78 | key | PrototypePollutionUtility/tests.js:289:50:289:78 | path ? ... y : key | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:39 | src | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | -| PrototypePollutionUtility/tests.js:293:41:293:43 | key | PrototypePollutionUtility/tests.js:293:37:293:44 | src[key] | | PrototypePollutionUtility/tests.js:301:27:301:29 | dst | PrototypePollutionUtility/tests.js:306:34:306:36 | dst | | PrototypePollutionUtility/tests.js:301:27:301:29 | dst | PrototypePollutionUtility/tests.js:306:34:306:36 | dst | | PrototypePollutionUtility/tests.js:301:27:301:29 | dst | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | @@ -2017,6 +2007,241 @@ edges | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | | PrototypePollutionUtility/tests.js:357:38:357:40 | key | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | | PrototypePollutionUtility/tests.js:357:38:357:40 | key | PrototypePollutionUtility/tests.js:357:31:357:41 | source[key] | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:22:367:24 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:367:31:367:33 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | PrototypePollutionUtility/tests.js:373:22:373:24 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | PrototypePollutionUtility/tests.js:373:22:373:24 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | PrototypePollutionUtility/tests.js:383:23:383:25 | key | +| PrototypePollutionUtility/tests.js:367:22:367:24 | key | PrototypePollutionUtility/tests.js:383:23:383:25 | key | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:367:31:367:33 | key | PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | +| PrototypePollutionUtility/tests.js:367:31:367:33 | key | PrototypePollutionUtility/tests.js:367:27:367:34 | obj[key] | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:375:32:375:34 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:375:32:375:34 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:372:29:372:31 | dst | PrototypePollutionUtility/tests.js:377:13:377:15 | dst | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | PrototypePollutionUtility/tests.js:375:42:375:44 | src | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | PrototypePollutionUtility/tests.js:375:42:375:44 | src | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | PrototypePollutionUtility/tests.js:377:24:377:26 | src | +| PrototypePollutionUtility/tests.js:372:34:372:36 | src | PrototypePollutionUtility/tests.js:377:24:377:26 | src | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:375:36:375:38 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:375:36:375:38 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:375:46:375:48 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:375:46:375:48 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:17:377:19 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:28:377:30 | key | +| PrototypePollutionUtility/tests.js:373:22:373:24 | key | PrototypePollutionUtility/tests.js:377:28:377:30 | key | +| PrototypePollutionUtility/tests.js:375:32:375:34 | dst | PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:32:375:34 | dst | PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | PrototypePollutionUtility/tests.js:372:29:372:31 | dst | +| PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | PrototypePollutionUtility/tests.js:372:29:372:31 | dst | +| PrototypePollutionUtility/tests.js:375:36:375:38 | key | PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:36:375:38 | key | PrototypePollutionUtility/tests.js:375:32:375:39 | dst[key] | +| PrototypePollutionUtility/tests.js:375:42:375:44 | src | PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:375:42:375:44 | src | PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | PrototypePollutionUtility/tests.js:372:34:372:36 | src | +| PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | PrototypePollutionUtility/tests.js:372:34:372:36 | src | +| PrototypePollutionUtility/tests.js:375:46:375:48 | key | PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:375:46:375:48 | key | PrototypePollutionUtility/tests.js:375:42:375:49 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:24:377:26 | src | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:377:28:377:30 | key | PrototypePollutionUtility/tests.js:377:24:377:31 | src[key] | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:385:33:385:35 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:385:33:385:35 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:382:30:382:32 | dst | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | +| PrototypePollutionUtility/tests.js:382:35:382:37 | src | PrototypePollutionUtility/tests.js:383:17:383:19 | src | +| PrototypePollutionUtility/tests.js:382:35:382:37 | src | PrototypePollutionUtility/tests.js:383:17:383:19 | src | +| PrototypePollutionUtility/tests.js:383:17:383:19 | src | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:383:17:383:19 | src | PrototypePollutionUtility/tests.js:383:28:383:32 | value | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:385:37:385:39 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:385:37:385:39 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:383:23:383:25 | key | PrototypePollutionUtility/tests.js:387:17:387:19 | key | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:385:43:385:47 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:385:43:385:47 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:383:28:383:32 | value | PrototypePollutionUtility/tests.js:387:24:387:28 | value | +| PrototypePollutionUtility/tests.js:385:33:385:35 | dst | PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:33:385:35 | dst | PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | PrototypePollutionUtility/tests.js:382:30:382:32 | dst | +| PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | PrototypePollutionUtility/tests.js:382:30:382:32 | dst | +| PrototypePollutionUtility/tests.js:385:37:385:39 | key | PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:37:385:39 | key | PrototypePollutionUtility/tests.js:385:33:385:40 | dst[key] | +| PrototypePollutionUtility/tests.js:385:43:385:47 | value | PrototypePollutionUtility/tests.js:382:35:382:37 | src | +| PrototypePollutionUtility/tests.js:385:43:385:47 | value | PrototypePollutionUtility/tests.js:382:35:382:37 | src | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:399:34:399:36 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:399:34:399:36 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:396:31:396:33 | dst | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | +| PrototypePollutionUtility/tests.js:396:36:396:38 | src | PrototypePollutionUtility/tests.js:398:33:398:35 | src | +| PrototypePollutionUtility/tests.js:396:36:396:38 | src | PrototypePollutionUtility/tests.js:398:33:398:35 | src | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:398:38:398:40 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:399:39:399:41 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:17:403:19 | key | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:401:42:401:46 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:13:398:41 | value | PrototypePollutionUtility/tests.js:403:24:403:28 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | PrototypePollutionUtility/tests.js:398:13:398:41 | value | +| PrototypePollutionUtility/tests.js:398:33:398:35 | src | PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:33:398:35 | src | PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:38:398:40 | key | PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:398:38:398:40 | key | PrototypePollutionUtility/tests.js:398:21:398:41 | wrapped ... c, key) | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:399:13:399:42 | target | PrototypePollutionUtility/tests.js:401:34:401:39 | target | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | PrototypePollutionUtility/tests.js:399:13:399:42 | target | +| PrototypePollutionUtility/tests.js:399:34:399:36 | dst | PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:34:399:36 | dst | PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:39:399:41 | key | PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:399:39:399:41 | key | PrototypePollutionUtility/tests.js:399:22:399:42 | wrapped ... t, key) | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:401:34:401:39 | target | PrototypePollutionUtility/tests.js:396:31:396:33 | dst | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:401:42:401:46 | value | PrototypePollutionUtility/tests.js:396:36:396:38 | src | +| PrototypePollutionUtility/tests.js:413:34:413:36 | dst | PrototypePollutionUtility/tests.js:416:37:416:39 | dst | +| PrototypePollutionUtility/tests.js:413:34:413:36 | dst | PrototypePollutionUtility/tests.js:420:13:420:15 | dst | +| PrototypePollutionUtility/tests.js:413:34:413:36 | dst | PrototypePollutionUtility/tests.js:420:13:420:15 | dst | +| PrototypePollutionUtility/tests.js:413:39:413:41 | src | PrototypePollutionUtility/tests.js:415:36:415:38 | src | +| PrototypePollutionUtility/tests.js:413:39:413:41 | src | PrototypePollutionUtility/tests.js:415:36:415:38 | src | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:415:41:415:43 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:415:41:415:43 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:416:42:416:44 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:416:42:416:44 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:17:420:19 | key | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:418:45:418:49 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:13:415:44 | value | PrototypePollutionUtility/tests.js:420:24:420:28 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | PrototypePollutionUtility/tests.js:415:13:415:44 | value | +| PrototypePollutionUtility/tests.js:415:36:415:38 | src | PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:36:415:38 | src | PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:415:41:415:43 | key | PrototypePollutionUtility/tests.js:415:21:415:44 | almostS ... c, key) | +| PrototypePollutionUtility/tests.js:416:13:416:45 | target | PrototypePollutionUtility/tests.js:418:37:418:42 | target | +| PrototypePollutionUtility/tests.js:416:13:416:45 | target | PrototypePollutionUtility/tests.js:418:37:418:42 | target | +| PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | PrototypePollutionUtility/tests.js:416:13:416:45 | target | +| PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | PrototypePollutionUtility/tests.js:416:13:416:45 | target | +| PrototypePollutionUtility/tests.js:416:37:416:39 | dst | PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | +| PrototypePollutionUtility/tests.js:416:42:416:44 | key | PrototypePollutionUtility/tests.js:416:22:416:45 | almostS ... t, key) | +| PrototypePollutionUtility/tests.js:418:37:418:42 | target | PrototypePollutionUtility/tests.js:413:34:413:36 | dst | +| PrototypePollutionUtility/tests.js:418:37:418:42 | target | PrototypePollutionUtility/tests.js:413:34:413:36 | dst | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:418:45:418:49 | value | PrototypePollutionUtility/tests.js:413:39:413:41 | src | +| PrototypePollutionUtility/tests.js:430:33:430:35 | src | PrototypePollutionUtility/tests.js:432:30:432:32 | src | +| PrototypePollutionUtility/tests.js:430:33:430:35 | src | PrototypePollutionUtility/tests.js:432:30:432:32 | src | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:431:14:431:16 | key | PrototypePollutionUtility/tests.js:437:17:437:19 | key | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:435:39:435:43 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:13:432:38 | value | PrototypePollutionUtility/tests.js:437:24:437:28 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | PrototypePollutionUtility/tests.js:432:13:432:38 | value | +| PrototypePollutionUtility/tests.js:432:30:432:32 | src | PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:432:30:432:32 | src | PrototypePollutionUtility/tests.js:432:21:432:38 | safeRead(src, key) | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | PrototypePollutionUtility/tests.js:430:33:430:35 | src | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | PrototypePollutionUtility/tests.js:430:33:430:35 | src | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | PrototypePollutionUtility/tests.js:430:33:430:35 | src | +| PrototypePollutionUtility/tests.js:435:39:435:43 | value | PrototypePollutionUtility/tests.js:430:33:430:35 | src | | examples/PrototypePollutionUtility.js:1:16:1:18 | dst | examples/PrototypePollutionUtility.js:5:19:5:21 | dst | | examples/PrototypePollutionUtility.js:1:16:1:18 | dst | examples/PrototypePollutionUtility.js:5:19:5:21 | dst | | examples/PrototypePollutionUtility.js:1:16:1:18 | dst | examples/PrototypePollutionUtility.js:7:13:7:15 | dst | @@ -2136,4 +2361,7 @@ edges | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | PrototypePollutionUtility/tests.js:276:34:276:36 | key | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:276:21:276:23 | src | src | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | dst | | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | PrototypePollutionUtility/tests.js:302:14:302:16 | key | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:302:21:302:23 | src | src | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | dst | | PrototypePollutionUtility/tests.js:322:17:322:19 | dst | PrototypePollutionUtility/tests.js:315:14:315:16 | key | PrototypePollutionUtility/tests.js:322:17:322:19 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:315:21:315:23 | src | src | PrototypePollutionUtility/tests.js:322:17:322:19 | dst | dst | +| PrototypePollutionUtility/tests.js:387:13:387:15 | dst | PrototypePollutionUtility/tests.js:365:14:365:16 | key | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:365:21:365:23 | obj | obj | PrototypePollutionUtility/tests.js:387:13:387:15 | dst | dst | +| PrototypePollutionUtility/tests.js:403:13:403:15 | dst | PrototypePollutionUtility/tests.js:397:14:397:16 | key | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:397:21:397:23 | src | src | PrototypePollutionUtility/tests.js:403:13:403:15 | dst | dst | +| PrototypePollutionUtility/tests.js:420:13:420:15 | dst | PrototypePollutionUtility/tests.js:414:14:414:16 | key | PrototypePollutionUtility/tests.js:420:13:420:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:414:21:414:23 | src | src | PrototypePollutionUtility/tests.js:420:13:420:15 | dst | dst | | examples/PrototypePollutionUtility.js:7:13:7:15 | dst | examples/PrototypePollutionUtility.js:2:14:2:16 | key | examples/PrototypePollutionUtility.js:7:13:7:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | examples/PrototypePollutionUtility.js:2:21:2:23 | src | src | examples/PrototypePollutionUtility.js:7:13:7:15 | dst | dst | diff --git a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/tests.js b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/tests.js index 57f080770d9..0c59890bd9c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/tests.js +++ b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/tests.js @@ -360,3 +360,81 @@ function mergePlainObjectsOnly(target, source) { } return target; } + +function forEachProp(obj, callback) { + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + callback(key, obj[key]); + } + } +} + +function mergeUsingCallback(dst, src) { + forEachProp(src, key => { + if (dst[key]) { + mergeUsingCallback(dst[key], src[key]); + } else { + dst[key] = src[key]; // NOT OK - but not currently flagged + } + }); +} + +function mergeUsingCallback2(dst, src) { + forEachProp(src, (key, value) => { + if (dst[key]) { + mergeUsingCallback2(dst[key], value); + } else { + dst[key] = value; // NOT OK + } + }); +} + +function wrappedRead(obj, key) { + return obj[key]; +} + +function copyUsingWrappedRead(dst, src) { + for (let key in src) { + let value = wrappedRead(src, key); + let target = wrappedRead(dst, key); + if (target) { + copyUsingWrappedRead(target, value); + } else { + dst[key] = value; // NOT OK + } + } +} + +function almostSafeRead(obj, key) { + if (key === '__proto__') return undefined; + return obj[key]; +} + +function copyUsingAlmostSafeRead(dst, src) { + for (let key in src) { + let value = almostSafeRead(src, key); + let target = almostSafeRead(dst, key); + if (target) { + copyUsingAlmostSafeRead(target, value); + } else { + dst[key] = value; // NOT OK + } + } +} + +function safeRead(obj, key) { + if (key === '__proto__' || key === 'constructor') return undefined; + return obj[key]; +} + +function copyUsingSafeRead(dst, src) { + for (let key in src) { + let value = safeRead(src, key); + let target = safeRead(dst, key); + if (target) { + copyUsingSafeRead(target, value); + } else { + dst[key] = value; // OK + } + } +} From 3ccdaa94adef97d596efc75cfed9cbd3d2e4c771 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 4 Feb 2020 15:04:10 +0000 Subject: [PATCH 137/148] JS: Expose argumentPassing as DataFlow::argumentPassingStep --- .../src/Security/CWE-400/PrototypePollutionUtility.ql | 10 +++++----- .../ql/src/semmle/javascript/dataflow/DataFlow.qll | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index 5275b487f9c..28ff0799f71 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -180,8 +180,8 @@ predicate dynamicPropReadStep(Node base, Node key, SourceNode output) { keyParam.flowsTo(innerKey) and innerOutput.flowsTo(callee.getAReturnedExpr().flow()) and call.getACallee() = callee and - argumentPassing(call, base, callee, baseParam) and - argumentPassing(call, key, callee, keyParam) and + argumentPassingStep(call, base, callee, baseParam) and + argumentPassingStep(call, key, callee, keyParam) and output = call ) } @@ -198,12 +198,12 @@ predicate isEnumeratedPropName(Node node) { | node = pred.getASuccessor() or - argumentPassing(_, pred, _, node) + argumentPassingStep(_, pred, _, node) or // Handle one level of callbacks exists(FunctionNode function, ParameterNode callback, int i | pred = callback.getAnInvocation().getArgument(i) and - argumentPassing(_, function, _, callback) and + argumentPassingStep(_, function, _, callback) and node = function.getParameter(i) ) ) @@ -223,7 +223,7 @@ predicate isPotentiallyObjectPrototype(SourceNode node) { exists(Node use | isPotentiallyObjectPrototype(use.getALocalSource()) | - argumentPassing(_, use, _, node) + argumentPassingStep(_, use, _, node) ) } diff --git a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll index b8589c21393..b1588a43c3f 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll @@ -20,6 +20,7 @@ import javascript private import internal.CallGraphs +private import internal.FlowSteps as FlowSteps module DataFlow { cached @@ -1470,6 +1471,8 @@ module DataFlow { ) } + predicate argumentPassingStep = FlowSteps::argumentPassing/4; + /** * Gets the data flow node representing the source of definition `def`, taking * flow through IIFE calls into account. From cf815351a9e16637ba5ee71f777d85980d3fae6e Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Tue, 4 Feb 2020 16:18:35 +0100 Subject: [PATCH 138/148] Java: Elaborate change note. --- change-notes/1.24/analysis-java.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/change-notes/1.24/analysis-java.md b/change-notes/1.24/analysis-java.md index 0c742201970..1598bf16039 100644 --- a/change-notes/1.24/analysis-java.md +++ b/change-notes/1.24/analysis-java.md @@ -10,11 +10,11 @@ The following changes in version 1.24 affect Java analysis in all applications. | **Query** | **Tags** | **Purpose** | |-----------------------------|-----------|--------------------------------------------------------------------| -| Disabled Spring CSRF protection (`java/spring-disabled-csrf-protection`) | security, external/cwe/cwe-352 | Finds disabled Cross-Site Request Forgery (CSRF) protection in Spring. | +| Disabled Spring CSRF protection (`java/spring-disabled-csrf-protection`) | security, external/cwe/cwe-352 | Finds disabled Cross-Site Request Forgery (CSRF) protection in Spring. Results are shown on LGTM by default. | | Failure to use HTTPS or SFTP URL in Maven artifact upload/download (`java/maven/non-https-url`) | security, external/cwe/cwe-300, external/cwe/cwe-319, external/cwe/cwe-494, external/cwe/cwe-829 | Finds use of insecure protocols during Maven dependency resolution. Results are shown on LGTM by default. | -| LDAP query built from user-controlled sources (`java/ldap-injection`) | security, external/cwe/cwe-090 | Finds LDAP queries vulnerable to injection of unsanitized user-controlled input. | +| LDAP query built from user-controlled sources (`java/ldap-injection`) | security, external/cwe/cwe-090 | Finds LDAP queries vulnerable to injection of unsanitized user-controlled input. Results are shown on LGTM by default. | | Left shift by more than the type width (`java/lshift-larger-than-type-width`) | correctness | Finds left shifts of ints by 32 bits or more and left shifts of longs by 64 bits or more. Results are shown on LGTM by default. | -| Suspicious date format (`java/suspicious-date-format`) | correctness | Finds date format patterns that use placeholders that are likely to be incorrect. | +| Suspicious date format (`java/suspicious-date-format`) | correctness | Finds date format patterns that use placeholders that are likely to be incorrect. Results are shown on LGTM by default. | ## Changes to existing queries From db2212e33ec60f9541da41824a1472e61eea37c4 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 4 Feb 2020 15:31:30 +0000 Subject: [PATCH 139/148] TS: Only print number of errors if there were any --- javascript/extractor/lib/typescript/src/main.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index cd2a112ef83..188544a3792 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -316,7 +316,9 @@ function handleOpenProjectCommand(command: OpenProjectCommand) { let diagnostics = program.getSemanticDiagnostics() .filter(d => d.category === ts.DiagnosticCategory.Error); - console.warn('TypeScript: reported ' + diagnostics.length + ' semantic errors.'); + if (diagnostics.length > 0) { + console.warn('TypeScript: reported ' + diagnostics.length + ' semantic errors.'); + } for (let diagnostic of diagnostics) { let text = diagnostic.messageText; if (text && typeof text !== 'string') { From b4df03767d8bead01421d67920cacc3cf3cc65f5 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 4 Feb 2020 16:36:41 +0000 Subject: [PATCH 140/148] JS: Ignore obvious Array.prototype.concat calls --- .../ql/src/semmle/javascript/StringConcatenation.qll | 7 +++++++ .../ql/test/library-tests/StringConcatenation/tst.js | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/StringConcatenation.qll b/javascript/ql/src/semmle/javascript/StringConcatenation.qll index 528085f50bf..941e6f6b908 100644 --- a/javascript/ql/src/semmle/javascript/StringConcatenation.qll +++ b/javascript/ql/src/semmle/javascript/StringConcatenation.qll @@ -55,6 +55,13 @@ module StringConcatenation { exists(DataFlow::MethodCallNode call | node = call and call.getMethodName() = "concat" and + not ( + exists(DataFlow::ArrayCreationNode array | + array.flowsTo(call.getAnArgument()) or array.flowsTo(call.getReceiver()) + ) + or + DataFlow::reflectiveCallNode(_) = call + ) and ( n = 0 and result = call.getReceiver() diff --git a/javascript/ql/test/library-tests/StringConcatenation/tst.js b/javascript/ql/test/library-tests/StringConcatenation/tst.js index 7aac7010f00..d1e70fb7239 100644 --- a/javascript/ql/test/library-tests/StringConcatenation/tst.js +++ b/javascript/ql/test/library-tests/StringConcatenation/tst.js @@ -95,3 +95,7 @@ function concatCall() { x = x.concat('two', 'three'); return x; } + +function arrayConcat(a, b) { + return [].concat(a, b); +} From 861d5eb86bbfedac813d79f7bd5c5681231614db Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 4 Feb 2020 10:29:52 -0800 Subject: [PATCH 141/148] C++: update tests after merge --- .../dataflow/security-taint/tainted_diff.expected | 8 +++++++- .../dataflow/security-taint/tainted_ir.expected | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected index 0202ee895d8..bd82e48f8c6 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected @@ -1,11 +1,17 @@ +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:6:40:33 | ! ... | IR only | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:7:40:12 | call to strcmp | IR only | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:7:40:33 | (bool)... | IR only | | test.cpp:49:23:49:28 | call to getenv | test.cpp:50:15:50:24 | envStr_ptr | AST only | | test.cpp:49:23:49:28 | call to getenv | test.cpp:50:28:50:40 | & ... | AST only | | test.cpp:49:23:49:28 | call to getenv | test.cpp:50:29:50:40 | envStrGlobal | AST only | | test.cpp:49:23:49:28 | call to getenv | test.cpp:52:2:52:12 | * ... | AST only | | test.cpp:49:23:49:28 | call to getenv | test.cpp:52:3:52:12 | envStr_ptr | AST only | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:10:64:14 | bytes | IR only | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:23 | call to strlen | IR only | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:37 | (int)... | IR only | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:37 | ... + ... | IR only | | test.cpp:68:28:68:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only | | test.cpp:68:28:68:33 | call to getenv | test.cpp:67:7:67:13 | copying | AST only | | test.cpp:68:28:68:33 | call to getenv | test.cpp:69:10:69:13 | copy | AST only | -| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:5:70:10 | call to strcpy | AST only | | test.cpp:68:28:68:33 | call to getenv | test.cpp:70:12:70:15 | copy | AST only | | test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | copy | AST only | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected index 95643564b9f..216d583d925 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected @@ -14,6 +14,9 @@ | test.cpp:38:23:38:28 | call to getenv | test.cpp:38:14:38:19 | envStr | | | test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:28 | call to getenv | | | test.cpp:38:23:38:28 | call to getenv | test.cpp:38:23:38:40 | (const char *)... | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:6:40:33 | ! ... | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:7:40:12 | call to strcmp | | +| test.cpp:38:23:38:28 | call to getenv | test.cpp:40:7:40:33 | (bool)... | | | test.cpp:38:23:38:28 | call to getenv | test.cpp:40:14:40:19 | envStr | | | test.cpp:49:23:49:28 | call to getenv | test.cpp:8:24:8:25 | s1 | | | test.cpp:49:23:49:28 | call to getenv | test.cpp:45:13:45:24 | envStrGlobal | | @@ -29,11 +32,16 @@ | test.cpp:60:29:60:34 | call to getenv | test.cpp:60:18:60:25 | userName | | | test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:34 | call to getenv | | | test.cpp:60:29:60:34 | call to getenv | test.cpp:60:29:60:47 | (const char *)... | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:10:64:14 | bytes | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:23 | call to strlen | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:37 | (int)... | | +| test.cpp:60:29:60:34 | call to getenv | test.cpp:64:18:64:37 | ... + ... | | | test.cpp:60:29:60:34 | call to getenv | test.cpp:64:25:64:32 | userName | | | test.cpp:68:28:68:33 | call to getenv | test.cpp:11:36:11:37 | s2 | | | test.cpp:68:28:68:33 | call to getenv | test.cpp:68:17:68:24 | userName | | | test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:33 | call to getenv | | | test.cpp:68:28:68:33 | call to getenv | test.cpp:68:28:68:46 | (const char *)... | | +| test.cpp:68:28:68:33 | call to getenv | test.cpp:70:5:70:10 | call to strcpy | | | test.cpp:68:28:68:33 | call to getenv | test.cpp:70:18:70:25 | userName | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:15:22:15:25 | nptr | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi | | From ac2e89317b5b29a35764506a5c79dd850e360472 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 4 Feb 2020 10:41:30 -0800 Subject: [PATCH 142/148] C++: autoformat --- .../test/library-tests/dataflow/security-taint/tainted_diff.ql | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.ql b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.ql index f76aac99707..9a90a898d7f 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.ql +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.ql @@ -13,5 +13,4 @@ where not AST::taintedIncludingGlobalVars(source, tainted, _) and not tainted.getLocation().getFile().getExtension() = "h" and side = "IR only" - select source, tainted, side From 1576bcfa3f1adce5df4354cc6645b2f6181d3699 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 4 Feb 2020 12:08:03 -0800 Subject: [PATCH 143/148] C++: remove unused predicates --- .../cpp/ir/dataflow/DefaultTaintTracking.qll | 4 +-- .../cpp/ir/dataflow/internal/DataFlowUtil.qll | 32 ------------------- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll index 2e5a742b796..fea47e1b5bd 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -172,7 +172,7 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) { any(CallInstruction call | exists(int indexIn | modelTaintToReturnValue(call.getStaticCallTarget(), indexIn) and - i1 = DataFlow::getACallArgumentOrIndirection(call, indexIn) + i1 = getACallArgumentOrIndirection(call, indexIn) ) ) or @@ -185,7 +185,7 @@ private predicate instructionTaintStep(Instruction i1, Instruction i2) { any(WriteSideEffectInstruction outNode | exists(CallInstruction call, int indexIn, int indexOut | modelTaintToParameter(call.getStaticCallTarget(), indexIn, indexOut) and - i1 = DataFlow::getACallArgumentOrIndirection(call, indexIn) and + i1 = getACallArgumentOrIndirection(call, indexIn) and outNode.getIndex() = indexOut and outNode.getPrimaryInstruction() = call ) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll index 5cca7894997..9428935ad7d 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll @@ -337,38 +337,6 @@ private predicate modelFlow(Instruction iFrom, Instruction iTo) { ) } -/** - * Get an instruction that goes into argument `argumentIndex` of `call`. This - * can be either directly or through one pointer indirection. - */ -Instruction getACallArgumentOrIndirection(CallInstruction call, int argumentIndex) { - result = call.getPositionalArgument(argumentIndex) - or - exists(ReadSideEffectInstruction readSE | - // TODO: why are read side effect operands imprecise? - result = readSE.getSideEffectOperand().getAnyDef() and - readSE.getPrimaryInstruction() = call and - readSE.getIndex() = argumentIndex - ) -} - -private predicate modelFlowToParameter(Function f, int parameterIn, int parameterOut) { - exists(FunctionInput modelIn, FunctionOutput modelOut | - f.(DataFlowFunction).hasDataFlow(modelIn, modelOut) and - (modelIn.isParameter(parameterIn) or modelIn.isParameterDeref(parameterIn)) and - modelOut.isParameterDeref(parameterOut) - ) -} - -private predicate modelFlowToReturnValue(Function f, int parameterIn) { - // Data flow from parameter to return value - exists(FunctionInput modelIn, FunctionOutput modelOut | - f.(DataFlowFunction).hasDataFlow(modelIn, modelOut) and - (modelIn.isParameter(parameterIn) or modelIn.isParameterDeref(parameterIn)) and - (modelOut.isReturnValue() or modelOut.isReturnValueDeref()) - ) -} - /** * Holds if data flows from `source` to `sink` in zero or more local * (intra-procedural) steps. From cec6646846a2b9f97592b52360be8abb355dde39 Mon Sep 17 00:00:00 2001 From: Matthew Gretton-Dann Date: Thu, 16 Jan 2020 14:27:25 +0000 Subject: [PATCH 144/148] C++: Update for EDG 6.0 behaviour change EDG 6.0 has changed how much information it gives about invalid expressions. Changing the output of this test. --- cpp/ql/test/library-tests/permissive/calls.expected | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp/ql/test/library-tests/permissive/calls.expected b/cpp/ql/test/library-tests/permissive/calls.expected index 35e31c1b691..9d780e76405 100644 --- a/cpp/ql/test/library-tests/permissive/calls.expected +++ b/cpp/ql/test/library-tests/permissive/calls.expected @@ -1,2 +1 @@ -| non_permissive.cpp:6:3:6:3 | call to f | non_permissive.cpp:2:13:2:13 | f | | permissive.cpp:6:3:6:3 | call to f | permissive.cpp:2:13:2:13 | f | From 1b67f479188accb8004faf4182df26f5975cb8b3 Mon Sep 17 00:00:00 2001 From: Matthew Gretton-Dann Date: Thu, 16 Jan 2020 14:28:18 +0000 Subject: [PATCH 145/148] C++: Update with improved location information EDG 6.0 gives better location in some circumstances changing the results of these tests for the better. --- .../locations/overloaded_operators/locations.expected | 2 +- cpp/ql/test/query-tests/definitions/definitions.expected | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/ql/test/library-tests/locations/overloaded_operators/locations.expected b/cpp/ql/test/library-tests/locations/overloaded_operators/locations.expected index e512cb4794c..7e105ce5085 100644 --- a/cpp/ql/test/library-tests/locations/overloaded_operators/locations.expected +++ b/cpp/ql/test/library-tests/locations/overloaded_operators/locations.expected @@ -1,4 +1,4 @@ -| test.cpp:9:12:9:35 | call to MyInt | +| test.cpp:9:5:9:36 | call to MyInt | | test.cpp:26:18:26:23 | call to MyInt | | test.cpp:42:15:42:15 | call to operator+ | | test.cpp:43:5:43:5 | call to operator+= | diff --git a/cpp/ql/test/query-tests/definitions/definitions.expected b/cpp/ql/test/query-tests/definitions/definitions.expected index 497daf3da05..1da060770e8 100644 --- a/cpp/ql/test/query-tests/definitions/definitions.expected +++ b/cpp/ql/test/query-tests/definitions/definitions.expected @@ -42,7 +42,7 @@ | class.cpp:91:27:91:29 | num | class.cpp:87:17:87:19 | num | V | | class.cpp:100:24:100:34 | type mention | class.cpp:94:18:94:28 | string_type | T | | class.cpp:105:1:105:15 | type mention | class.cpp:97:7:97:21 | StringContainer | T | -| class.cpp:106:9:106:23 | type mention | class.cpp:100:2:100:16 | StringContainer | M | +| class.cpp:106:9:106:23 | type mention | class.cpp:97:7:97:21 | StringContainer | T | | class.cpp:106:25:106:27 | STR(x) | class.cpp:95:1:95:18 | #define STR(x) L ## x | X | | class.cpp:117:2:117:29 | type mention | class.cpp:109:7:109:34 | myClassWithConstructorParams | T | | class.cpp:117:37:117:37 | a | class.cpp:115:27:115:27 | a | V | From b60190857788326676a3a7b80b02a7b870019b3b Mon Sep 17 00:00:00 2001 From: Matthew Gretton-Dann Date: Mon, 27 Jan 2020 16:39:17 +0000 Subject: [PATCH 146/148] CPP: Update for changes in EDG IL. --- cpp/ql/test/library-tests/ir/ir/PrintAST.expected | 6 +++--- cpp/ql/test/library-tests/ir/ir/raw_ir.expected | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected index 7778ebb76c0..343ffc27db5 100644 --- a/cpp/ql/test/library-tests/ir/ir/PrintAST.expected +++ b/cpp/ql/test/library-tests/ir/ir/PrintAST.expected @@ -5753,9 +5753,9 @@ ir.cpp: # 851| 0: [VariableDeclarationEntry] definition of d # 851| Type = [Struct] PolymorphicDerived # 851| init: [Initializer] initializer for d -# 851| expr: [ConstructorCall] call to PolymorphicDerived -# 851| Type = [VoidType] void -# 851| ValueCategory = prvalue +#-----| expr: [ConstructorCall] call to PolymorphicDerived +#-----| Type = [VoidType] void +#-----| ValueCategory = prvalue # 853| 2: [DeclStmt] declaration # 853| 0: [VariableDeclarationEntry] definition of pb # 853| Type = [PointerType] PolymorphicBase * diff --git a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected index 2583f634374..f8435117823 100644 --- a/cpp/ql/test/library-tests/ir/ir/raw_ir.expected +++ b/cpp/ql/test/library-tests/ir/ir/raw_ir.expected @@ -4217,10 +4217,10 @@ ir.cpp: #-----| mu0_4(PolymorphicBase) = ^IndirectMayWriteSideEffect[-1] : &:r850_1 # 851| r851_1(glval) = VariableAddress[d] : # 851| mu851_2(PolymorphicDerived) = Uninitialized[d] : &:r851_1 -# 851| r851_3(glval) = FunctionAddress[PolymorphicDerived] : -# 851| v851_4(void) = Call : func:r851_3, this:r851_1 -# 851| mu851_5(unknown) = ^CallSideEffect : ~mu849_3 -# 851| mu851_6(PolymorphicDerived) = ^IndirectMayWriteSideEffect[-1] : &:r851_1 +#-----| r0_5(glval) = FunctionAddress[PolymorphicDerived] : +#-----| v0_6(void) = Call : func:r0_5, this:r851_1 +#-----| mu0_7(unknown) = ^CallSideEffect : ~mu849_3 +#-----| mu0_8(PolymorphicDerived) = ^IndirectMayWriteSideEffect[-1] : &:r851_1 # 853| r853_1(glval) = VariableAddress[pb] : # 853| r853_2(glval) = VariableAddress[b] : # 853| r853_3(PolymorphicBase *) = CopyValue : r853_2 From fd9975db85c72c23e7c0a6126c2f233b9c644dbb Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Wed, 5 Feb 2020 09:47:51 +0000 Subject: [PATCH 147/148] JS: Address comments --- .../CWE-400/PrototypePollutionUtility.ql | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index 28ff0799f71..ee9919118a6 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -163,7 +163,7 @@ class DynamicPropRead extends DataFlow::SourceNode, DataFlow::ValueNode { /** * Holds if `output` is the result of `base[key]`, either directly or through - * one or more function calls. + * one or more function calls, ignoring reads that can't access the prototype chain. */ predicate dynamicPropReadStep(Node base, Node key, SourceNode output) { exists(DynamicPropRead read | @@ -217,7 +217,12 @@ predicate isPotentiallyObjectPrototype(SourceNode node) { exists(Node base, Node key | dynamicPropReadStep(base, key, node) and isEnumeratedPropName(key) and - not arePropertiesEnumerated(base.getALocalSource()) // ignore `for (let key in src) { ... src[key] ... }` + + // Ignore cases where the properties of `base` are enumerated, to avoid FPs + // where the key came from that enumeration (and thus will not return Object.prototype). + // For example, `src[key]` in `for (let key in src) { ... src[key] ... }` will generally + // not return Object.prototype because `key` is an enumerable property of `src`. + not arePropertiesEnumerated(base.getALocalSource()) ) or exists(Node use | @@ -241,11 +246,15 @@ predicate dynamicPropWrite(DataFlow::Node base, DataFlow::Node prop, DataFlow::N exists(AssignExpr write, IndexExpr index | index = write.getLhs() and base = index.getBase().flow() and - isPotentiallyObjectPrototype(base.getALocalSource()) and prop = index.getPropertyNameExpr().flow() and rhs = write.getRhs().flow() and not exists(prop.getStringValue()) and - not arePropertiesEnumerated(base.getALocalSource()) + not arePropertiesEnumerated(base.getALocalSource()) and + + // Prune writes that are unlikely to modify Object.prototype. + // This is mainly for performance, but may block certain results due to + // not tracking out of function returns and into callbacks. + isPotentiallyObjectPrototype(base.getALocalSource()) ) } From cf18bd7bb85f816b063922d1bae96e4f98474630 Mon Sep 17 00:00:00 2001 From: Asger F Date: Wed, 5 Feb 2020 09:48:16 +0000 Subject: [PATCH 148/148] Update javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql Co-Authored-By: Esben Sparre Andreasen --- javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index ee9919118a6..376e2e16f56 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -173,7 +173,7 @@ predicate dynamicPropReadStep(Node base, Node key, SourceNode output) { output = read ) or - // Summarize functions returning a dynamic property read of two parameters. + // Summarize functions returning a dynamic property read of two parameters, such as `function getProp(obj, prop) { return obj[prop]; }`. exists(CallNode call, Function callee, ParameterNode baseParam, ParameterNode keyParam, Node innerBase, Node innerKey, SourceNode innerOutput | dynamicPropReadStep(innerBase, innerKey, innerOutput) and baseParam.flowsTo(innerBase) and