mirror of
https://github.com/github/codeql.git
synced 2025-12-24 04:36:35 +01:00
JS: Add flow summaries for Arrays
This commit is contained in:
@@ -9,7 +9,7 @@ module ArrayTaintTracking {
|
||||
/**
|
||||
* A taint propagating data flow edge caused by the builtin array functions.
|
||||
*/
|
||||
private class ArrayFunctionTaintStep extends TaintTracking::SharedTaintStep {
|
||||
private class ArrayFunctionTaintStep extends TaintTracking::LegacyTaintStep {
|
||||
override predicate arrayStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
arrayFunctionTaintStep(pred, succ, _)
|
||||
}
|
||||
@@ -114,7 +114,7 @@ private module ArrayDataFlow {
|
||||
* A step modeling the creation of an Array using the `Array.from(x)` method.
|
||||
* The step copies the elements of the argument (set, array, or iterator elements) into the resulting array.
|
||||
*/
|
||||
private class ArrayFrom extends DataFlow::SharedFlowStep {
|
||||
private class ArrayFrom extends DataFlow::LegacyFlowStep {
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
|
||||
) {
|
||||
@@ -134,7 +134,7 @@ private module ArrayDataFlow {
|
||||
*
|
||||
* Such a step can occur both with the `push` and `unshift` methods, or when creating a new array.
|
||||
*/
|
||||
private class ArrayCopySpread extends DataFlow::SharedFlowStep {
|
||||
private class ArrayCopySpread extends DataFlow::LegacyFlowStep {
|
||||
override predicate loadStoreStep(
|
||||
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
|
||||
) {
|
||||
@@ -155,7 +155,7 @@ private module ArrayDataFlow {
|
||||
/**
|
||||
* A step for storing an element on an array using `arr.push(e)` or `arr.unshift(e)`.
|
||||
*/
|
||||
private class ArrayAppendStep extends DataFlow::SharedFlowStep {
|
||||
private class ArrayAppendStep extends DataFlow::LegacyFlowStep {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
prop = arrayElement() and
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
@@ -186,7 +186,7 @@ private module ArrayDataFlow {
|
||||
* A step for reading/writing an element from an array inside a for-loop.
|
||||
* E.g. a read from `foo[i]` to `bar` in `for(var i = 0; i < arr.length; i++) {bar = foo[i]}`.
|
||||
*/
|
||||
private class ArrayIndexingStep extends DataFlow::SharedFlowStep {
|
||||
private class ArrayIndexingStep extends DataFlow::LegacyFlowStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
exists(ArrayIndexingAccess access |
|
||||
prop = arrayElement() and
|
||||
@@ -208,7 +208,7 @@ private module ArrayDataFlow {
|
||||
* A step for retrieving an element from an array using `.pop()`, `.shift()`, or `.at()`.
|
||||
* E.g. `array.pop()`.
|
||||
*/
|
||||
private class ArrayPopStep extends DataFlow::SharedFlowStep {
|
||||
private class ArrayPopStep extends DataFlow::LegacyFlowStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["pop", "shift", "at"] and
|
||||
@@ -229,7 +229,7 @@ private module ArrayDataFlow {
|
||||
*
|
||||
* And the second parameter in the callback is the array ifself, so there is a `loadStoreStep` from the array to that second parameter.
|
||||
*/
|
||||
private class ArrayIteration extends PreCallGraphStep {
|
||||
private class ArrayIteration extends LegacyPreCallGraphStep {
|
||||
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["map", "forEach"] and
|
||||
@@ -261,7 +261,7 @@ private module ArrayDataFlow {
|
||||
/**
|
||||
* A step for creating an array and storing the elements in the array.
|
||||
*/
|
||||
private class ArrayCreationStep extends PreCallGraphStep {
|
||||
private class ArrayCreationStep extends LegacyPreCallGraphStep {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
exists(DataFlow::ArrayCreationNode array, int i |
|
||||
element = array.getElement(i) and
|
||||
@@ -275,7 +275,7 @@ private module ArrayDataFlow {
|
||||
* A step modeling that `splice` can insert elements into an array.
|
||||
* For example in `array.splice(i, del, e)`: if `e` is tainted, then so is `array
|
||||
*/
|
||||
private class ArraySpliceStep extends DataFlow::SharedFlowStep {
|
||||
private class ArraySpliceStep extends DataFlow::LegacyFlowStep {
|
||||
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = "splice" and
|
||||
@@ -290,7 +290,7 @@ private module ArrayDataFlow {
|
||||
* A step for modeling `concat`.
|
||||
* For example in `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
|
||||
*/
|
||||
private class ArrayConcatStep extends DataFlow::SharedFlowStep {
|
||||
private class ArrayConcatStep extends DataFlow::LegacyFlowStep {
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = "concat" and
|
||||
@@ -304,7 +304,7 @@ private module ArrayDataFlow {
|
||||
/**
|
||||
* A step for modeling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`.
|
||||
*/
|
||||
private class ArraySliceStep extends DataFlow::SharedFlowStep {
|
||||
private class ArraySliceStep extends DataFlow::LegacyFlowStep {
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
exists(DataFlow::MethodCallNode call |
|
||||
call.getMethodName() = ["slice", "splice", "filter"] and
|
||||
@@ -318,7 +318,7 @@ private module ArrayDataFlow {
|
||||
/**
|
||||
* A step modeling that elements from an array `arr` are received by calling `find`.
|
||||
*/
|
||||
private class ArrayFindStep extends DataFlow::SharedFlowStep {
|
||||
private class ArrayFindStep extends DataFlow::LegacyFlowStep {
|
||||
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = arrayFindCall(pred) and
|
||||
@@ -368,7 +368,7 @@ private module ArrayLibraries {
|
||||
/**
|
||||
* A taint step through the `arrify` library, or other libraries that (maybe) convert values into arrays.
|
||||
*/
|
||||
private class ArrayifyStep extends TaintTracking::SharedTaintStep {
|
||||
private class ArrayifyStep extends TaintTracking::LegacyTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(API::CallNode call | call = API::moduleImport(["arrify", "array-ify"]).getACall() |
|
||||
pred = call.getArgument(0) and succ = call
|
||||
@@ -388,7 +388,7 @@ private module ArrayLibraries {
|
||||
/**
|
||||
* A taint step for a library that copies the elements of an array into another array.
|
||||
*/
|
||||
private class ArrayCopyTaint extends TaintTracking::SharedTaintStep {
|
||||
private class ArrayCopyTaint extends TaintTracking::LegacyTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = arrayCopyCall(pred) and
|
||||
@@ -400,7 +400,7 @@ private module ArrayLibraries {
|
||||
/**
|
||||
* A loadStoreStep for a library that copies the elements of an array into another array.
|
||||
*/
|
||||
private class ArrayCopyLoadStore extends DataFlow::SharedFlowStep {
|
||||
private class ArrayCopyLoadStore extends DataFlow::LegacyFlowStep {
|
||||
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
|
||||
exists(DataFlow::CallNode call |
|
||||
call = arrayCopyCall(pred) and
|
||||
@@ -413,7 +413,7 @@ private module ArrayLibraries {
|
||||
/**
|
||||
* A taint step through a call to `Array.prototype.flat` or a polyfill implementing array flattening.
|
||||
*/
|
||||
private class ArrayFlatStep extends TaintTracking::SharedTaintStep {
|
||||
private class ArrayFlatStep extends TaintTracking::LegacyTaintStep {
|
||||
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::CallNode call | succ = call |
|
||||
call.(DataFlow::MethodCallNode).getMethodName() = "flat" and
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
private import AmbiguousCoreMethods
|
||||
private import Arrays2
|
||||
|
||||
@@ -0,0 +1,577 @@
|
||||
/**
|
||||
* Contains a summary for relevant methods on arrays, except Array.prototype.join which is currently special-cased in StringConcatenation.qll.
|
||||
*
|
||||
* Note that some of Array methods are modelled in `AmbiguousCoreMethods.qll`, and `join` and `toString` are special-cased elsewhere.
|
||||
*/
|
||||
|
||||
private import javascript
|
||||
private import semmle.javascript.dataflow.FlowSummary
|
||||
private import semmle.javascript.dataflow.InferredTypes
|
||||
private import semmle.javascript.dataflow.internal.DataFlowPrivate as Private
|
||||
private import FlowSummaryUtil
|
||||
|
||||
pragma[nomagic]
|
||||
DataFlow::SourceNode arrayConstructorRef() { result = DataFlow::globalVarRef("Array") }
|
||||
|
||||
pragma[nomagic]
|
||||
private int firstSpreadIndex(ArrayExpr expr) {
|
||||
result = min(int i | expr.getElement(i) instanceof SpreadElement)
|
||||
}
|
||||
|
||||
/**
|
||||
* Store and read steps for an array literal. Since literals are not seen as calls, this is not a flow summary.
|
||||
*
|
||||
* In case of spread elements `[x, ...y]`, we generate a read from `y -> ...y` and then a store from `...y` into
|
||||
* the array literal (to ensure constant-indices get broken up).
|
||||
*/
|
||||
class ArrayLiteralStep extends DataFlow::AdditionalFlowStep {
|
||||
override predicate storeStep(
|
||||
DataFlow::Node pred, DataFlow::ContentSet contents, DataFlow::Node succ
|
||||
) {
|
||||
exists(ArrayExpr array, int i |
|
||||
pred = array.getElement(i).flow() and
|
||||
succ = array.flow()
|
||||
|
|
||||
if i >= firstSpreadIndex(array)
|
||||
then contents = DataFlow::ContentSet::arrayElement() // after a spread operator, store into unknown indices
|
||||
else contents = DataFlow::ContentSet::arrayElementFromInt(i)
|
||||
)
|
||||
}
|
||||
|
||||
override predicate readStep(
|
||||
DataFlow::Node pred, DataFlow::ContentSet contents, DataFlow::Node succ
|
||||
) {
|
||||
exists(SpreadElement spread |
|
||||
spread = any(ArrayExpr array).getAnElement() and
|
||||
pred = spread.getOperand().flow() and
|
||||
succ = spread.flow() and
|
||||
contents = DataFlow::ContentSet::arrayElement()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private predicate isForLoopVariable(Variable v) {
|
||||
v.getADeclarationStatement() = any(ForStmt stmt).getInit()
|
||||
or
|
||||
// Handle the somewhat rare case: `for (v; ...; ++v) { ... }`
|
||||
v.getADeclaration() = any(ForStmt stmt).getInit()
|
||||
}
|
||||
|
||||
private predicate isLikelyArrayIndex(Expr e) {
|
||||
// Require that 'e' is of type number and refers to a for-loop variable.
|
||||
// TODO: This is here to mirror the old behaviour. Experiment with turning the 'and' into an 'or'.
|
||||
TTNumber() = unique(InferredType type | type = e.flow().analyze().getAType()) and
|
||||
isForLoopVariable(e.(VarAccess).getVariable())
|
||||
or
|
||||
e.(PropAccess).getPropertyName() = "length"
|
||||
}
|
||||
|
||||
/**
|
||||
* A dynamic property store `obj[e] = rhs` seen as a potential array access.
|
||||
*
|
||||
* We need to restrict to cases where `e` is likely to be an array index, as
|
||||
* propagating data between arbitrary unknown property accesses is too imprecise.
|
||||
*/
|
||||
class DynamicArrayStoreStep extends DataFlow::AdditionalFlowStep {
|
||||
override predicate storeStep(
|
||||
DataFlow::Node pred, DataFlow::ContentSet contents, DataFlow::Node succ
|
||||
) {
|
||||
exists(Assignment assignment, IndexExpr lvalue |
|
||||
lvalue = assignment.getLhs() and
|
||||
not exists(lvalue.getPropertyName()) and
|
||||
isLikelyArrayIndex(lvalue.getPropertyNameExpr()) and
|
||||
contents = DataFlow::ContentSet::arrayElement() and
|
||||
succ.(DataFlow::ExprPostUpdateNode).getPreUpdateNode() = lvalue.getBase().flow()
|
||||
|
|
||||
pred = assignment.(Assignment).getRhs().flow()
|
||||
or
|
||||
// for compound assignments, use the result of the operator
|
||||
pred = assignment.(CompoundAssignExpr).flow()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayConstructorSummary extends SummarizedCallable {
|
||||
ArrayConstructorSummary() { this = "Array constructor" }
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result = arrayConstructorRef().getAnInvocation()
|
||||
}
|
||||
|
||||
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"
|
||||
)
|
||||
or
|
||||
// TODO: workaround for WithArrayElement not being converted to a taint step
|
||||
preservesValue = false and
|
||||
input = "Argument[arguments-array]" and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
class CopyWithin extends SummarizedCallable {
|
||||
CopyWithin() { this = "Array#copyWithin" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = "copyWithin" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[this].WithArrayElement" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
// TODO: workaround for WithArrayElement not being converted to a taint step
|
||||
preservesValue = false and
|
||||
input = "Argument[this]" and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
class FlowIntoCallback extends SummarizedCallable {
|
||||
FlowIntoCallback() { this = "Array method with flow into callback" }
|
||||
|
||||
override InstanceCall getACallSimple() {
|
||||
result.getMethodName() = ["every", "findIndex", "findLastIndex", "some"]
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "Argument[0].Parameter[0]"
|
||||
or
|
||||
input = "Argument[1]" and
|
||||
output = "Argument[0].Parameter[this]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Filter extends SummarizedCallable {
|
||||
Filter() { this = "Array#filter" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = "filter" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "Argument[0].Parameter[0]"
|
||||
or
|
||||
input = "Argument[1]" and
|
||||
output = "Argument[0].Parameter[this]"
|
||||
or
|
||||
// Note: in case the filter condition acts as a barrier/sanitizer,
|
||||
// it is up to the query to mark the 'filter' call as a barrier/sanitizer
|
||||
input = "Argument[this].WithArrayElement" and
|
||||
output = "ReturnValue"
|
||||
)
|
||||
or
|
||||
// TODO: workaround for WithArrayElement not being converted to a taint step
|
||||
preservesValue = false and
|
||||
input = "Argument[this]" and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
class Fill extends SummarizedCallable {
|
||||
Fill() { this = "Array#fill" } // TODO: clear contents if no interval is given
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = "fill" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[0..]" and
|
||||
output = ["ReturnValue.ArrayElement", "Argument[this].ArrayElement"]
|
||||
}
|
||||
}
|
||||
|
||||
class FindLike extends SummarizedCallable {
|
||||
FindLike() { this = "Array#find / Array#findLast" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = ["find", "findLast"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = ["Argument[0].Parameter[0]", "ReturnValue"]
|
||||
or
|
||||
input = "Argument[1]" and
|
||||
output = "Argument[0].Parameter[this]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class FindLibrary extends SummarizedCallable {
|
||||
FindLibrary() { this = "'array.prototype.find' / 'array-find'" }
|
||||
|
||||
override DataFlow::CallNode getACallSimple() {
|
||||
result = DataFlow::moduleImport(["array.prototype.find", "array-find"]).getACall()
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[0].ArrayElement" and
|
||||
output = ["Argument[1].Parameter[0]", "ReturnValue"]
|
||||
or
|
||||
input = "Argument[2]" and
|
||||
output = "Argument[1].Parameter[this]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Flat extends SummarizedCallable {
|
||||
private int depth;
|
||||
|
||||
Flat() { this = "Array#flat(" + depth + ")" and depth in [1 .. 3] }
|
||||
|
||||
override InstanceCall getACallSimple() {
|
||||
result.getMethodName() = "flat" and
|
||||
(
|
||||
result.getNumArgument() = 1 and
|
||||
result.getArgument(0).getIntValue() = depth
|
||||
or
|
||||
depth = 1 and
|
||||
result.getNumArgument() = 0
|
||||
)
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[this]" + concat(int n | n in [0 .. depth] | ".ArrayElement")
|
||||
or
|
||||
exists(int partialDepth | partialDepth in [1 .. depth - 1] |
|
||||
input =
|
||||
"Argument[this]" + concat(int n | n in [0 .. partialDepth] | ".ArrayElement") +
|
||||
".WithoutArrayElement"
|
||||
)
|
||||
) and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
}
|
||||
}
|
||||
|
||||
class FlatMap extends SummarizedCallable {
|
||||
FlatMap() { this = "Array#flatMap" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = "flatMap" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "Argument[0].Parameter[0]"
|
||||
or
|
||||
input = "Argument[this]" and
|
||||
output = "Argument[0].Parameter[2]"
|
||||
or
|
||||
input = "Argument[1]" and
|
||||
output = "Argument[0].Parameter[1]"
|
||||
or
|
||||
input = "Argument[0].ReturnValue." + ["ArrayElement", "WithoutArrayElement"] and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private DataFlow::CallNode arrayFromCall() {
|
||||
// TODO: update fromAsync model when async iterators are supported
|
||||
result = arrayConstructorRef().getAMemberCall(["from", "fromAsync"])
|
||||
or
|
||||
result = DataFlow::moduleImport("array-from").getACall()
|
||||
}
|
||||
|
||||
class From1Arg extends SummarizedCallable {
|
||||
From1Arg() { this = "Array.from(arg)" }
|
||||
|
||||
override DataFlow::CallNode getACallSimple() {
|
||||
result = arrayFromCall() and result.getNumArgument() = 1
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[0].WithArrayElement" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
input = "Argument[0]." + ["SetElement", "IteratorElement"] and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
or
|
||||
input = "Argument[0].MapKey" and
|
||||
output = "ReturnValue.ArrayElement.Member[0]"
|
||||
or
|
||||
input = "Argument[0].MapValue" and
|
||||
output = "ReturnValue.ArrayElement.Member[1]"
|
||||
or
|
||||
input = "Argument[0].IteratorError" and
|
||||
output = "ReturnValue[exception]"
|
||||
)
|
||||
or
|
||||
// TODO: we currently convert ArrayElement read/store steps to taint steps, but this does not
|
||||
// work for WithArrayElement because it's just an expectsContent node, and there's no way easy
|
||||
// to omit the expectsContent restriction in taint tracking.
|
||||
// Work around this for now.
|
||||
preservesValue = false and
|
||||
input = "Argument[0]" and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
class FromManyArg extends SummarizedCallable {
|
||||
FromManyArg() { this = "Array.from(arg, callback, [thisArg])" }
|
||||
|
||||
override DataFlow::CallNode getACallSimple() {
|
||||
result = arrayFromCall() and
|
||||
result.getNumArgument() > 1
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[0]." + ["ArrayElement", "SetElement", "IteratorElement"] and
|
||||
output = "Argument[1].Parameter[0]"
|
||||
or
|
||||
input = "Argument[0].MapKey" and
|
||||
output = "Argument[1].Parameter[0].Member[0]"
|
||||
or
|
||||
input = "Argument[0].MapValue" and
|
||||
output = "Argument[1].Parameter[0].Member[1]"
|
||||
or
|
||||
input = "Argument[1].ReturnValue" and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
or
|
||||
input = "Argument[2]" and
|
||||
output = "Argument[1].Parameter[this]"
|
||||
or
|
||||
input = "Argument[0].IteratorError" and
|
||||
output = "ReturnValue[exception]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Map extends SummarizedCallable {
|
||||
Map() { this = "Array#map" }
|
||||
|
||||
override InstanceCall getACallSimple() {
|
||||
// Note that this summary may spuriously apply to library methods named `map` such as from lodash/underscore.
|
||||
// However, this will not cause spurious flow, because for such functions, the first argument will be an array, not a callback,
|
||||
// and every part of the summary below uses Argument[0] in a way that requires it to be a callback.
|
||||
result.getMethodName() = "map"
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "Argument[0].Parameter[0]"
|
||||
or
|
||||
input = "Argument[this]" and
|
||||
output = "Argument[0].Parameter[2]"
|
||||
or
|
||||
input = "Argument[1]" and
|
||||
output = "Argument[0].Parameter[this]"
|
||||
or
|
||||
input = "Argument[0].ReturnValue" and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Of extends SummarizedCallable {
|
||||
Of() { this = "Array.of" }
|
||||
|
||||
override DataFlow::CallNode getACallSimple() {
|
||||
result = arrayConstructorRef().getAMemberCall("of")
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[0..]" and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
}
|
||||
}
|
||||
|
||||
class Pop extends SummarizedCallable {
|
||||
Pop() { this = "Array#pop" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = "pop" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
class PushLike extends SummarizedCallable {
|
||||
PushLike() { this = "Array#push / Array#unshift" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = ["push", "unshift"] }
|
||||
|
||||
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
|
||||
output = "Argument[this].ArrayElement"
|
||||
}
|
||||
}
|
||||
|
||||
class ReduceLike extends SummarizedCallable {
|
||||
ReduceLike() { this = "Array#reduce / Array#reduceRight" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = ["reduce", "reduceRight"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
/*
|
||||
* Signatures:
|
||||
* reduce(callbackFn, [initialValue])
|
||||
* callbackfn(accumulator, currentValue, index, array)
|
||||
*/
|
||||
|
||||
(
|
||||
input = ["Argument[1]", "Argument[0].ReturnValue"] and
|
||||
output = "Argument[0].Parameter[0]" // accumulator
|
||||
or
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "Argument[0].Parameter[1]" // currentValue
|
||||
or
|
||||
input = "Argument[this]" and
|
||||
output = "Argument[0].Parameter[3]" // array
|
||||
or
|
||||
input = "Argument[0].ReturnValue" and
|
||||
output = "ReturnValue"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Reverse extends SummarizedCallable {
|
||||
Reverse() { this = "Array#reverse / Array#toReversed" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = ["reverse", "toReversed"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
}
|
||||
}
|
||||
|
||||
class Shift extends SummarizedCallable {
|
||||
Shift() { this = "Array#shift" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = "shift" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
class Sort extends SummarizedCallable {
|
||||
Sort() { this = "Array#sort / Array#toSorted" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = ["sort", "toSorted"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
or
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "Argument[0].Parameter[0,1]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class Splice extends SummarizedCallable {
|
||||
Splice() { this = "Array#splice" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = "splice" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
or
|
||||
input = "Argument[2..]" and
|
||||
output = ["Argument[this].ArrayElement", "ReturnValue.ArrayElement"]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ToSpliced extends SummarizedCallable {
|
||||
ToSpliced() { this = "Array#toSpliced" }
|
||||
|
||||
override InstanceCall getACallSimple() { result.getMethodName() = "toSpliced" }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[this].ArrayElement" and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
or
|
||||
input = "Argument[2..]" and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayCoercionPackage extends FunctionalPackageSummary {
|
||||
ArrayCoercionPackage() { this = "ArrayCoercionPackage" }
|
||||
|
||||
override string getAPackageName() { result = ["arrify", "array-ify"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
(
|
||||
input = "Argument[0].WithArrayElement" and
|
||||
output = "ReturnValue"
|
||||
or
|
||||
input = "Argument[0].WithoutArrayElement" and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
)
|
||||
or
|
||||
// TODO: workaround for WithArrayElement not being converted to a taint step
|
||||
preservesValue = false and
|
||||
input = "Argument[0]" and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayCopyingPackage extends FunctionalPackageSummary {
|
||||
ArrayCopyingPackage() { this = "ArrayCopyingPackage" }
|
||||
|
||||
override string getAPackageName() { result = ["array-union", "array-uniq", "uniq"] }
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
preservesValue = true and
|
||||
input = "Argument[0..].ArrayElement" and
|
||||
output = "ReturnValue.ArrayElement"
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayFlatteningPackage extends FunctionalPackageSummary {
|
||||
ArrayFlatteningPackage() { this = "ArrayFlatteningPackage" }
|
||||
|
||||
override string getAPackageName() {
|
||||
result = ["array-flatten", "arr-flatten", "flatten", "array.prototype.flat"]
|
||||
}
|
||||
|
||||
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
|
||||
// TODO: properly support these. For the moment we're just adding parity with the old model
|
||||
preservesValue = false and
|
||||
input = "Argument[0..]" and
|
||||
output = "ReturnValue"
|
||||
}
|
||||
}
|
||||
@@ -13,3 +13,22 @@ class InstanceCall extends DataFlow::CallNode {
|
||||
/** Gets the name of method being invoked */
|
||||
string getMethodName() { result = this.getCalleeName() }
|
||||
}
|
||||
|
||||
/**
|
||||
* A summary a function that is the default export from an NPM package.
|
||||
*/
|
||||
abstract class FunctionalPackageSummary extends SummarizedCallable {
|
||||
bindingset[this]
|
||||
FunctionalPackageSummary() { any() }
|
||||
|
||||
/** Gets a name of a package for which this summary applies. */
|
||||
abstract string getAPackageName();
|
||||
|
||||
override DataFlow::InvokeNode getACallSimple() {
|
||||
result = DataFlow::moduleImport(this.getAPackageName()).getAnInvocation()
|
||||
}
|
||||
|
||||
override DataFlow::InvokeNode getACall() {
|
||||
result = API::moduleImport(this.getAPackageName()).getAnInvocation()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user