JS: Add flow summaries for Arrays

This commit is contained in:
Asger F
2023-10-03 13:14:29 +02:00
parent a31e251529
commit 4319b07798
4 changed files with 613 additions and 16 deletions

View File

@@ -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

View File

@@ -1 +1,2 @@
private import AmbiguousCoreMethods
private import Arrays2

View File

@@ -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"
}
}

View File

@@ -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()
}
}