mirror of
https://github.com/github/codeql.git
synced 2026-07-20 02:38:23 +02:00
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:
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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() + ".."
|
||||
}
|
||||
|
||||
|
||||
@@ -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()]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user