Merge pull request #17213 from asgerf/jss/spread-argument

JS: Improve handling of spread arguments and rest parameters [shared data flow branch]
This commit is contained in:
Asger F
2024-09-09 13:15:22 +02:00
committed by GitHub
52 changed files with 1257 additions and 330 deletions

View File

@@ -1780,8 +1780,7 @@ module DataFlow {
)
}
/** A load step from a reflective parameter node to each parameter. */
private class ReflectiveParamsStep extends PreCallGraphStep {
private class ReflectiveParamsStep extends LegacyPreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f, int i |
f.getFunction() = params.getFunction() and
@@ -1793,7 +1792,7 @@ module DataFlow {
}
/** A taint step from the reflective parameters node to any parameter. */
private class ReflectiveParamsTaintStep extends TaintTracking::SharedTaintStep {
private class ReflectiveParamsTaintStep extends TaintTracking::LegacyTaintStep {
override predicate step(DataFlow::Node obj, DataFlow::Node element) {
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f |
f.getFunction() = params.getFunction() and

View File

@@ -260,6 +260,14 @@ module TaintTracking {
)
}
private class HeapLegacyTaintStep extends LegacyTaintStep {
override predicate heapStep(DataFlow::Node pred, DataFlow::Node succ) {
// arrays with tainted elements are tainted (in old data flow)
succ.(DataFlow::ArrayCreationNode).getAnElement() = pred and
not any(PromiseAllCreation call).getArrayNode() = succ
}
}
/**
* A taint propagating data flow edge through object or array elements and
* promises.
@@ -274,10 +282,6 @@ module TaintTracking {
// spreading a tainted value into an array literal gives a tainted array
succ.(DataFlow::ArrayCreationNode).getASpreadArgument() = pred
or
// arrays with tainted elements and objects with tainted property names are tainted
succ.(DataFlow::ArrayCreationNode).getAnElement() = pred and
not any(PromiseAllCreation call).getArrayNode() = succ
or
// reading from a tainted object yields a tainted result
succ.(DataFlow::PropRead).getBase() = pred and
not (

View File

@@ -255,7 +255,10 @@ module Public {
Content asSingleton() { this = MkSingletonContent(result) }
/** Gets the property name to be accessed. */
PropertyName asPropertyName() { result = this.asSingleton().asPropertyName() }
PropertyName asPropertyName() {
// TODO: array indices should be mapped to a ContentSet that also reads from UnknownArrayElement
result = this.asSingleton().asPropertyName()
}
/** Gets the array index to be accessed. */
int asArrayIndex() { result = this.asSingleton().asArrayIndex() }

View File

@@ -24,6 +24,8 @@ private module ConsistencyConfig implements InputSig<Location, JSDataFlow> {
or
n instanceof FlowSummaryIntermediateAwaitStoreNode
or
n instanceof FlowSummaryDynamicParameterArrayNode
or
n instanceof GenericSynthesizedNode
or
n = DataFlow::globalAccessPathRootPseudoNode()
@@ -37,6 +39,16 @@ private module ConsistencyConfig implements InputSig<Location, JSDataFlow> {
isAmbientNode(call.asOrdinaryCall()) or
isAmbientNode(call.asAccessorCall())
}
predicate argHasPostUpdateExclude(ArgumentNode node) {
// Side-effects directly on these can't propagate back to the caller, and for longer access paths it's too imprecise
node instanceof TStaticArgumentArrayNode or
node instanceof TDynamicArgumentArrayNode
}
predicate reverseReadExclude(DataFlow::Node node) {
node instanceof FlowSummaryDynamicParameterArrayNode
}
}
module Consistency = MakeConsistency<Location, JSDataFlow, JSTaintFlow, ConsistencyConfig>;

View File

@@ -15,6 +15,12 @@ private import semmle.javascript.dataflow.internal.VariableCapture as VariableCa
cached
private module Cached {
private Content dynamicArgumentsContent() {
result.asArrayIndex() = [0 .. 10]
or
result.isUnknownArrayElement()
}
/**
* The raw data type underlying `DataFlow::Node`.
*/
@@ -33,6 +39,25 @@ private module Cached {
} or
TThisNode(StmtContainer f) { f.(Function).getThisBinder() = f or f instanceof TopLevel } or
TFunctionSelfReferenceNode(Function f) or
TStaticArgumentArrayNode(InvokeExpr node) or
TDynamicArgumentArrayNode(InvokeExpr node) { node.isSpreadArgument(_) } or
TStaticParameterArrayNode(Function f) {
f.getAParameter().isRestParameter() or f.usesArgumentsObject()
} or
TDynamicParameterArrayNode(Function f) or
/** Data about to be stored in the rest parameter object. Needed for shifting array indices. */
TRestParameterStoreNode(Function f, Content storeContent) {
f.getRestParameter().getIndex() > 0 and
storeContent = dynamicArgumentsContent()
} or
/** Data about to be stored in the dynamic argument array of an invocation. Needed for shifting array indices. */
TDynamicArgumentStoreNode(InvokeExpr invoke, Content storeContent) {
invoke.isSpreadArgument(_) and
storeContent = dynamicArgumentsContent()
} or
TApplyCallTaintNode(MethodCallExpr node) {
node.getMethodName() = "apply" and exists(node.getArgument(1))
} or
TDestructuredModuleImportNode(ImportDeclaration decl) {
exists(decl.getASpecifier().getImportedName())
} or
@@ -43,7 +68,7 @@ private module Cached {
TExceptionalInvocationReturnNode(InvokeExpr e) or
TGlobalAccessPathRoot() or
TTemplatePlaceholderTag(Templating::TemplatePlaceholderTag tag) or
TReflectiveParametersNode(Function f) or
TReflectiveParametersNode(Function f) { f.usesArgumentsObject() } or
TExprPostUpdateNode(AST::ValueNode e) {
e = any(InvokeExpr invoke).getAnArgument() or
e = any(PropAccess access).getBase() or
@@ -58,6 +83,7 @@ private module Cached {
TConstructorThisArgumentNode(InvokeExpr e) { e instanceof NewExpr or e instanceof SuperCall } or
TConstructorThisPostUpdate(Constructor ctor) or
TFlowSummaryNode(FlowSummaryImpl::Private::SummaryNode sn) or
TFlowSummaryDynamicParameterArrayNode(FlowSummaryImpl::Public::SummarizedCallable callable) or
TFlowSummaryIntermediateAwaitStoreNode(FlowSummaryImpl::Private::SummaryNode sn) {
// NOTE: This dependency goes through the 'Steps' module whose instantiation depends on the call graph,
// but the specific predicate we're referering to does not use that information.
@@ -96,7 +122,8 @@ private class TEarlyStageNode =
TFunctionSelfReferenceNode or TDestructuredModuleImportNode or THtmlAttributeNode or
TFunctionReturnNode or TExceptionalFunctionReturnNode or TExceptionalInvocationReturnNode or
TGlobalAccessPathRoot or TTemplatePlaceholderTag or TReflectiveParametersNode or
TExprPostUpdateNode or TConstructorThisArgumentNode;
TExprPostUpdateNode or TConstructorThisArgumentNode or TStaticArgumentArrayNode or
TDynamicArgumentArrayNode or TStaticParameterArrayNode or TDynamicParameterArrayNode;
/**
* A data-flow node that is not a flow summary node.

View File

@@ -30,6 +30,19 @@ class FlowSummaryNode extends DataFlow::Node, TFlowSummaryNode {
override string toString() { result = this.getSummaryNode().toString() }
}
class FlowSummaryDynamicParameterArrayNode extends DataFlow::Node,
TFlowSummaryDynamicParameterArrayNode
{
private FlowSummaryImpl::Public::SummarizedCallable callable;
FlowSummaryDynamicParameterArrayNode() { this = TFlowSummaryDynamicParameterArrayNode(callable) }
FlowSummaryImpl::Public::SummarizedCallable getSummarizedCallable() { result = callable }
cached
override string toString() { result = "[dynamic parameter array] " + callable }
}
class FlowSummaryIntermediateAwaitStoreNode extends DataFlow::Node,
TFlowSummaryIntermediateAwaitStoreNode
{
@@ -81,6 +94,131 @@ class GenericSynthesizedNode extends DataFlow::Node, TGenericSynthesizedNode {
string getTag() { result = tag }
}
/**
* An argument containing an array of all positional arguments with an obvious index, i.e. not affected by a spread argument.
*/
class StaticArgumentArrayNode extends DataFlow::Node, TStaticArgumentArrayNode {
private InvokeExpr invoke;
StaticArgumentArrayNode() { this = TStaticArgumentArrayNode(invoke) }
override StmtContainer getContainer() { result = invoke.getContainer() }
override string toString() { result = "[static argument array]" }
override Location getLocation() { result = invoke.getLocation() }
}
/**
* An argument containing an array of all positional arguments with non-obvious index, i.e. affected by a spread argument.
*
* Only exists for call sites with a spread argument.
*/
class DynamicArgumentArrayNode extends DataFlow::Node, TDynamicArgumentArrayNode {
private InvokeExpr invoke;
DynamicArgumentArrayNode() { this = TDynamicArgumentArrayNode(invoke) }
override StmtContainer getContainer() { result = invoke.getContainer() }
override string toString() { result = "[dynamic argument array]" }
override Location getLocation() { result = invoke.getLocation() }
}
/**
* Intermediate node with data that will be stored in `DyanmicArgumentArrayNode`.
*/
class DynamicArgumentStoreNode extends DataFlow::Node, TDynamicArgumentStoreNode {
private InvokeExpr invoke;
private Content content;
DynamicArgumentStoreNode() { this = TDynamicArgumentStoreNode(invoke, content) }
override StmtContainer getContainer() { result = invoke.getContainer() }
override string toString() { result = "[dynamic argument store node] content=" + content }
override Location getLocation() { result = invoke.getLocation() }
}
/**
* Intermediate node with data that will be stored in the function's rest parameter node.
*/
class RestParameterStoreNode extends DataFlow::Node, TRestParameterStoreNode {
private Function function;
private Content content;
RestParameterStoreNode() { this = TRestParameterStoreNode(function, content) }
override StmtContainer getContainer() { result = function }
override string toString() {
result =
"[rest parameter store node] '..." + function.getRestParameter().getName() + "' content=" +
content
}
override Location getLocation() { result = function.getRestParameter().getLocation() }
}
/**
* A parameter containing an array of all positional arguments with an obvious index, i.e. not affected by spread or `.apply()`.
*
* These are read and stored in the function's rest parameter and `arguments` array.
* The node only exists for functions with a rest parameter or which uses the `arguments` array.
*/
class StaticParameterArrayNode extends DataFlow::Node, TStaticParameterArrayNode {
private Function function;
StaticParameterArrayNode() { this = TStaticParameterArrayNode(function) }
override StmtContainer getContainer() { result = function }
override string toString() { result = "[static parameter array]" }
override Location getLocation() { result = function.getLocation() }
}
/**
* A parameter containing an array of all positional argument values with non-obvious index, i.e. affected by spread or `.apply()`.
*
* These are read and assigned into regular positional parameters and stored into rest parameters and the `arguments` array.
*/
class DynamicParameterArrayNode extends DataFlow::Node, TDynamicParameterArrayNode {
private Function function;
DynamicParameterArrayNode() { this = TDynamicParameterArrayNode(function) }
override StmtContainer getContainer() { result = function }
override string toString() { result = "[dynamic parameter array]" }
override Location getLocation() { result = function.getLocation() }
}
/**
* Node with taint input from the second argument of `.apply()` and with a store edge back into that same argument.
*
* This ensures that if `.apply()` is called with a tainted value (not inside a content) the taint is
* boxed in an `ArrayElement` content. This is necessary for the target function to propagate the taint.
*/
class ApplyCallTaintNode extends DataFlow::Node, TApplyCallTaintNode {
private MethodCallExpr apply;
ApplyCallTaintNode() { this = TApplyCallTaintNode(apply) }
override StmtContainer getContainer() { result = apply.getContainer() }
override string toString() { result = "[apply call taint node]" }
override Location getLocation() { result = apply.getArgument(1).getLocation() }
MethodCallExpr getMethodCallExpr() { result = apply }
DataFlow::Node getArrayNode() { result = apply.getArgument(1).flow() }
}
cached
newtype TReturnKind =
MkNormalReturnKind() or
@@ -220,19 +358,31 @@ abstract class LibraryCallable extends string {
}
private predicate isParameterNodeImpl(Node p, DataFlowCallable c, ParameterPosition pos) {
p = c.asSourceCallable().(Function).getParameter(pos.asPositional()).flow()
exists(Parameter parameter |
parameter = c.asSourceCallable().(Function).getParameter(pos.asPositional()) and
not parameter.isRestParameter() and
p = TValueNode(parameter)
)
or
pos.isThis() and p = TThisNode(c.asSourceCallable().(Function))
or
pos.isFunctionSelfReference() and p = TFunctionSelfReferenceNode(c.asSourceCallable())
or
pos.isArgumentsArray() and p = TReflectiveParametersNode(c.asSourceCallable())
pos.isStaticArgumentArray() and p = TStaticParameterArrayNode(c.asSourceCallable())
or
pos.isDynamicArgumentArray() and p = TDynamicParameterArrayNode(c.asSourceCallable())
or
exists(FlowSummaryNode summaryNode |
summaryNode = p and
FlowSummaryImpl::Private::summaryParameterNode(summaryNode.getSummaryNode(), pos) and
c.asLibraryCallable() = summaryNode.getSummarizedCallable()
)
or
exists(FlowSummaryImpl::Public::SummarizedCallable callable |
c.asLibraryCallable() = callable and
pos.isDynamicArgumentArray() and
p = TFlowSummaryDynamicParameterArrayNode(callable)
)
}
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
@@ -242,6 +392,12 @@ predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition
private predicate isArgumentNodeImpl(Node n, DataFlowCall call, ArgumentPosition pos) {
n = call.asOrdinaryCall().getArgument(pos.asPositional())
or
exists(InvokeExpr invoke |
call.asOrdinaryCall() = TReflectiveCallNode(invoke, "apply") and
pos.isDynamicArgumentArray() and
n = TValueNode(invoke.getArgument(1))
)
or
pos.isThis() and n = call.asOrdinaryCall().(DataFlow::CallNode).getReceiver()
or
exists(DataFlow::PartialInvokeNode invoke, DataFlow::Node callback |
@@ -268,10 +424,6 @@ private predicate isArgumentNodeImpl(Node n, DataFlowCall call, ArgumentPosition
or
pos.isThis() and n = TConstructorThisArgumentNode(call.asOrdinaryCall().asExpr())
or
// For now, treat all spread argument as flowing into the 'arguments' array, regardless of preceding arguments
n = call.asOrdinaryCall().getASpreadArgument() and
pos.isArgumentsArray()
or
// receiver of accessor call
pos.isThis() and n = call.asAccessorCall().getBase()
or
@@ -280,6 +432,14 @@ private predicate isArgumentNodeImpl(Node n, DataFlowCall call, ArgumentPosition
or
FlowSummaryImpl::Private::summaryArgumentNode(call.(SummaryCall).getReceiver(),
n.(FlowSummaryNode).getSummaryNode(), pos)
or
exists(InvokeExpr invoke | call.asOrdinaryCall() = TValueNode(invoke) |
n = TStaticArgumentArrayNode(invoke) and
pos.isStaticArgumentArray()
or
n = TDynamicArgumentArrayNode(invoke) and
pos.isDynamicArgumentArray()
)
}
predicate isArgumentNode(ArgumentNode n, DataFlowCall call, ArgumentPosition pos) {
@@ -291,6 +451,8 @@ DataFlowCallable nodeGetEnclosingCallable(Node node) {
or
result.asLibraryCallable() = node.(FlowSummaryNode).getSummarizedCallable()
or
result.asLibraryCallable() = node.(FlowSummaryDynamicParameterArrayNode).getSummarizedCallable()
or
result.asLibraryCallable() = node.(FlowSummaryIntermediateAwaitStoreNode).getSummarizedCallable()
or
node = TGenericSynthesizedNode(_, _, result)
@@ -347,6 +509,8 @@ predicate nodeIsHidden(Node node) {
or
node instanceof FlowSummaryNode
or
node instanceof FlowSummaryDynamicParameterArrayNode
or
node instanceof FlowSummaryIntermediateAwaitStoreNode
or
node instanceof CaptureNode
@@ -359,6 +523,18 @@ predicate nodeIsHidden(Node node) {
node.(DataFlow::ExprPostUpdateNode).getExpr() instanceof Function
or
node instanceof GenericSynthesizedNode
or
node instanceof StaticArgumentArrayNode
or
node instanceof DynamicArgumentArrayNode
or
node instanceof DynamicArgumentStoreNode
or
node instanceof StaticParameterArrayNode
or
node instanceof DynamicParameterArrayNode
or
node instanceof RestParameterStoreNode
}
predicate neverSkipInPathGraph(Node node) {
@@ -671,7 +847,8 @@ newtype TParameterPosition =
MkPositionalLowerBound(int n) { n = [0 .. getMaxArity()] } or
MkThisParameter() or
MkFunctionSelfReferenceParameter() or
MkArgumentsArrayParameter()
MkStaticArgumentArray() or
MkDynamicArgumentArray()
class ParameterPosition extends TParameterPosition {
predicate isPositionalExact() { this instanceof MkPositionalParameter }
@@ -688,7 +865,9 @@ class ParameterPosition extends TParameterPosition {
predicate isFunctionSelfReference() { this = MkFunctionSelfReferenceParameter() }
predicate isArgumentsArray() { this = MkArgumentsArrayParameter() }
predicate isStaticArgumentArray() { this = MkStaticArgumentArray() }
predicate isDynamicArgumentArray() { this = MkDynamicArgumentArray() }
string toString() {
result = this.asPositional().toString()
@@ -699,7 +878,9 @@ class ParameterPosition extends TParameterPosition {
or
this.isFunctionSelfReference() and result = "function"
or
this.isArgumentsArray() and result = "arguments-array"
this.isStaticArgumentArray() and result = "static-argument-array"
or
this.isDynamicArgumentArray() and result = "dynamic-argument-array"
}
}
@@ -860,6 +1041,39 @@ predicate simpleLocalFlowStep(Node node1, Node node2) {
or
// NOTE: For consistency with readStep/storeStep, we do not translate these steps to jump steps automatically.
DataFlow::AdditionalFlowStep::step(node1, node2)
or
exists(InvokeExpr invoke |
// When the first argument is a spread argument, flow into the argument array as a local flow step
// to ensure we preserve knowledge about array indices
node1 = TValueNode(invoke.getArgument(0).stripParens().(SpreadElement).getOperand()) and
node2 = TDynamicArgumentArrayNode(invoke)
)
or
exists(Function f |
// When the first parameter is a rest parameter, flow into the rest parameter as a local flow step
// to ensure we preserve knowledge about array indices
node1 = TStaticParameterArrayNode(f) or node1 = TDynamicParameterArrayNode(f)
|
// rest parameter at initial position
exists(Parameter rest |
rest = f.getParameter(0) and
rest.isRestParameter() and
node2 = TValueNode(rest)
)
or
// 'arguments' array
node2 = TReflectiveParametersNode(f)
)
or
// Prepare to store non-spread arguments after a spread into the dynamic arguments array
exists(InvokeExpr invoke, int n, Expr argument, Content storeContent |
invoke.getArgument(n) = argument and
not argument instanceof SpreadElement and
n > firstSpreadArgumentIndex(invoke) and
node1 = TValueNode(argument) and
node2 = TDynamicArgumentStoreNode(invoke, storeContent) and
storeContent.isUnknownArrayElement()
)
}
predicate localMustFlowStep(Node node1, Node node2) { node1 = node2.getImmediatePredecessor() }
@@ -925,6 +1139,57 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
)
or
DataFlow::AdditionalFlowStep::readStep(node1, c, node2)
or
// Pass dynamic arguments into plain parameters
exists(Function function, Parameter param, int n |
param = function.getParameter(n) and
not param.isRestParameter() and
node1 = TDynamicParameterArrayNode(function) and
node2 = TValueNode(param) and
c = ContentSet::arrayElementFromInt(n)
)
or
// Prepare to store dynamic and static arguments into the rest parameter array when it isn't the first parameter
exists(Function function, Content content, int restIndex |
restIndex = function.getRestParameter().getIndex() and
restIndex > 0 and
(node1 = TStaticParameterArrayNode(function) or node1 = TDynamicParameterArrayNode(function)) and
node2 = TRestParameterStoreNode(function, content)
|
// shift known array indices
c.asArrayIndex() = content.asArrayIndex() + restIndex
or
content.isUnknownArrayElement() and // TODO: don't read unknown array elements from static array
c = ContentSet::arrayElementUnknown()
)
or
// Prepare to store spread arguments into the dynamic arguments array, when it isn't the initial argument
exists(InvokeExpr invoke, int n, Expr argument, Content storeContent |
invoke.getArgument(n).stripParens().(SpreadElement).getOperand() = argument and
n > 0 and // n=0 is handled as a value step
node1 = TValueNode(argument) and
node2 = TDynamicArgumentStoreNode(invoke, storeContent) and
if n > firstSpreadArgumentIndex(invoke)
then
c = ContentSet::arrayElement() and // unknown start index when not the first spread operator
storeContent.isUnknownArrayElement()
else (
storeContent.asArrayIndex() = n + c.asArrayIndex()
or
storeContent.isUnknownArrayElement() and c.asSingleton() = storeContent
)
)
or
exists(FlowSummaryNode parameter, ParameterPosition pos |
FlowSummaryImpl::Private::summaryParameterNode(parameter.getSummaryNode(), pos) and
node1 = TFlowSummaryDynamicParameterArrayNode(parameter.getSummarizedCallable()) and
node2 = parameter and
(
c.asArrayIndex() = pos.asPositional()
or
c = ContentSet::arrayElementLowerBound(pos.asPositionalLowerBound())
)
)
}
/** Gets the post-update node for which `node` is the corresponding pre-update node. */
@@ -939,6 +1204,11 @@ private Node tryGetPostUpdate(Node node) {
result = node
}
pragma[nomagic]
private int firstSpreadArgumentIndex(InvokeExpr expr) {
result = min(int i | expr.isSpreadArgument(i))
}
/**
* Holds if data can flow from `node1` to `node2` via a store into `c`. Thus,
* `node2` references an object with a content `c.getAStoreContent()` that
@@ -970,6 +1240,31 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
)
or
DataFlow::AdditionalFlowStep::storeStep(node1, c, node2)
or
exists(Function f, Content storeContent |
node1 = TRestParameterStoreNode(f, storeContent) and
node2 = TValueNode(f.getRestParameter()) and
c.asSingleton() = storeContent
)
or
exists(InvokeExpr invoke, Content storeContent |
node1 = TDynamicArgumentStoreNode(invoke, storeContent) and
node2 = TDynamicArgumentArrayNode(invoke) and
c.asSingleton() = storeContent
)
or
exists(InvokeExpr invoke, int n |
node1 = TValueNode(invoke.getArgument(n)) and
node2 = TStaticArgumentArrayNode(invoke) and
c.asArrayIndex() = n and
not n >= firstSpreadArgumentIndex(invoke)
)
or
exists(ApplyCallTaintNode taintNode |
node1 = taintNode and
node2 = taintNode.getArrayNode() and
c = ContentSet::arrayElementUnknown()
)
}
/**
@@ -1019,6 +1314,9 @@ predicate expectsContent(Node n, ContentSet c) {
c = MkPromiseFilter()
or
any(AdditionalFlowInternal flow).expectsContent(n, c)
or
c = ContentSet::arrayElement() and
n instanceof TDynamicParameterArrayNode
}
abstract class NodeRegion extends Unit {

View File

@@ -35,8 +35,6 @@ private predicate positionName(ParameterPosition pos, string operand) {
or
pos.isFunctionSelfReference() and operand = "function"
or
pos.isArgumentsArray() and operand = "arguments-array"
or
operand = pos.asPositionalLowerBound() + ".."
}

View File

@@ -1,5 +1,6 @@
private import javascript
private import semmle.javascript.dataflow.internal.DataFlowPrivate
private import semmle.javascript.dataflow.internal.DataFlowNode
private import semmle.javascript.dataflow.internal.Contents::Public
private import semmle.javascript.dataflow.internal.sharedlib.FlowSummaryImpl as FlowSummaryImpl
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate as FlowSummaryPrivate
@@ -18,6 +19,19 @@ predicate defaultAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2)
or
FlowSummaryPrivate::Steps::summaryStoreStep(node1.(FlowSummaryNode).getSummaryNode(),
ContentSet::arrayElement(), node2.(FlowSummaryNode).getSummaryNode())
or
// If the spread argument itself is tainted (not inside a content), store it into the dynamic argument array.
exists(InvokeExpr invoke, Content c |
node1 = TValueNode(invoke.getAnArgument().stripParens().(SpreadElement).getOperand()) and
node2 = TDynamicArgumentStoreNode(invoke, c) and
c.isUnknownArrayElement()
)
or
// If the array in an .apply() call is tainted (not inside a content), box it in an array element (similar to the case above).
exists(ApplyCallTaintNode taintNode |
node1 = taintNode.getArrayNode() and
node2 = taintNode
)
}
predicate defaultAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2, string model) {
@@ -47,5 +61,5 @@ predicate defaultTaintSanitizer(DataFlow::Node node) {
bindingset[node]
predicate defaultImplicitTaintRead(DataFlow::Node node, ContentSet c) {
exists(node) and
c = ContentSet::promiseValue()
c = [ContentSet::promiseValue(), ContentSet::arrayElement()]
}

View File

@@ -142,7 +142,7 @@ module AsyncPackage {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::FunctionNode iteratee, IterationCall call |
iteratee = call.getIteratorCallback() and // Require a closure to avoid spurious call/return mismatch.
pred = call.getCollection() and
pred = call.getCollection() and // TODO: needs a flow summary to ensure ArrayElement content is unfolded
succ = iteratee.getParameter(0)
)
}

View File

@@ -52,6 +52,7 @@ module Markdown {
private class MarkdownTableStep extends MarkdownStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call | call = DataFlow::moduleImport("markdown-table").getACall() |
// TODO: needs a flow summary to ensure ArrayElement content is unfolded
succ = call and
pred = call.getArgument(0)
)

View File

@@ -1,7 +1,7 @@
/**
* Contains a summary for relevant methods on arrays, except Array.prototype.join which is currently special-cased in StringConcatenation.qll.
* Contains a summary for relevant methods on arrays.
*
* Note that some of Array methods are modelled in `AmbiguousCoreMethods.qll`, and `join` and `toString` are special-cased elsewhere.
* Note that some of Array methods are modelled in `AmbiguousCoreMethods.qll`, and `toString` is special-cased elsewhere.
*/
private import javascript
@@ -101,17 +101,31 @@ class ArrayConstructorSummary extends SummarizedCallable {
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
(
input = "Argument[0..]" and
output = "ReturnValue.ArrayElement"
or
input = "Argument[arguments-array].WithArrayElement" and
output = "ReturnValue"
)
input = "Argument[0..]" and
output = "ReturnValue.ArrayElement"
or
// TODO: workaround for WithArrayElement not being converted to a taint step
preservesValue = false and
input = "Argument[arguments-array]" and
input = "Argument[0..]" and
output = "ReturnValue"
}
}
/**
* A call to `join` with a separator argument.
*
* Calls without separators are modelled in `StringConcatenation.qll`.
*/
class Join extends SummarizedCallable {
Join() { this = "Array#join" }
override InstanceCall getACallSimple() {
result.getMethodName() = "join" and
result.getNumArgument() = [0, 1]
}
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = false and
input = "Argument[this].ArrayElement" and
output = "ReturnValue"
}
}
@@ -417,8 +431,7 @@ class PushLike extends SummarizedCallable {
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
preservesValue = true and
// TODO: make it so `arguments-array` is handled without needing to reference it explicitly in every flow-summary
input = ["Argument[0..]", "Argument[arguments-array].ArrayElement"] and
input = "Argument[0..]" and
output = "Argument[this].ArrayElement"
}
}

View File

@@ -54,7 +54,7 @@ module TaintedUrlSuffix {
// Inherit all ordinary taint steps except `x -> x.p` steps
srclbl = label() and
dstlbl = label() and
TaintTracking::sharedTaintStep(src, dst) and
TaintTracking::AdditionalTaintStep::step(src, dst) and
not isSafeLocationProp(dst)
or
// Transition from URL suffix to full taint when extracting the query/fragment part.