Merge branch 'main' into shellTrue

This commit is contained in:
Erik Krogh Kristensen
2021-03-24 20:09:23 +01:00
409 changed files with 6759 additions and 1284 deletions

View File

@@ -87,11 +87,6 @@ private DataFlow::Node goodRandom(DataFlow::TypeTracker t, DataFlow::SourceNode
or
exists(DataFlow::TypeTracker t2 | t = t2.smallstep(goodRandom(t2, source), result))
or
// re-using the collection steps for `Set`.
exists(DataFlow::TypeTracker t2 |
result = CollectionsTypeTracking::collectionStep(goodRandom(t2, source), t, t2)
)
or
InsecureRandomness::isAdditionalTaintStep(goodRandom(t.continue(), source), result) and
// bit shifts and multiplication by powers of two are generally used for constructing larger numbers from smaller numbers.
not exists(BinaryExpr binop | binop = result.asExpr() |

View File

@@ -6,7 +6,7 @@
* @kind problem
* @problem.severity warning
* @precision low
* @id js/actions/pull_request_target
* @id js/actions/pull-request-target
* @tags actions
* security
* external/cwe/cwe-094

View File

@@ -102,6 +102,7 @@ import semmle.javascript.frameworks.Next
import semmle.javascript.frameworks.NoSQL
import semmle.javascript.frameworks.PkgCloud
import semmle.javascript.frameworks.PropertyProjection
import semmle.javascript.frameworks.Puppeteer
import semmle.javascript.frameworks.React
import semmle.javascript.frameworks.ReactNative
import semmle.javascript.frameworks.Request

View File

@@ -10,6 +10,9 @@
import javascript
from DataFlow::InvokeNode invoke, Function f
where invoke.getACallee() = f
select invoke, "Call to $@", f, f.describe()
from DataFlow::Node invoke, Function f, string kind
where
invoke.(DataFlow::InvokeNode).getACallee() = f and kind = "Call"
or
invoke.(DataFlow::PropRef).getAnAccessorCallee().getFunction() = f and kind = "Accessor call"
select invoke, kind + " to $@", f, f.describe()

View File

@@ -15,17 +15,17 @@ predicate relevantStep(DataFlow::Node pred, DataFlow::Node succ) {
(
TaintTracking::sharedTaintStep(pred, succ)
or
any(DataFlow::AdditionalFlowStep cfg).step(pred, succ)
DataFlow::SharedFlowStep::step(pred, succ)
or
any(DataFlow::AdditionalFlowStep cfg).step(pred, succ, _, _)
DataFlow::SharedFlowStep::step(pred, succ, _, _)
or
any(DataFlow::AdditionalFlowStep cfg).loadStep(pred, succ, _)
DataFlow::SharedFlowStep::loadStep(pred, succ, _)
or
any(DataFlow::AdditionalFlowStep cfg).storeStep(pred, succ, _)
DataFlow::SharedFlowStep::storeStep(pred, succ, _)
or
any(DataFlow::AdditionalFlowStep cfg).loadStoreStep(pred, succ, _, _)
DataFlow::SharedFlowStep::loadStoreStep(pred, succ, _, _)
or
any(DataFlow::AdditionalFlowStep cfg).loadStoreStep(pred, succ, _)
DataFlow::SharedFlowStep::loadStoreStep(pred, succ, _)
) and
not pred.getFile() instanceof IgnoredFile and
not succ.getFile() instanceof IgnoredFile

View File

@@ -313,10 +313,7 @@ module API {
module Node {
/** Gets a node whose type has the given qualified name. */
Node ofType(string moduleName, string exportedName) {
exists(TypeName tn |
tn.hasQualifiedName(moduleName, exportedName) and
result = Impl::MkCanonicalNameUse(tn).(Node).getInstance()
)
result = Impl::MkHasUnderlyingType(moduleName, exportedName).(Node).getInstance()
}
}
@@ -384,6 +381,8 @@ module API {
imports(_, m)
or
m = any(CanonicalName n | isUsed(n)).getExternalModuleName()
or
any(TypeAnnotation n).hasQualifiedName(m, _)
} or
MkClassInstance(DataFlow::ClassNode cls) { cls = trackDefNode(_) and hasSemantics(cls) } or
MkAsyncFuncResult(DataFlow::FunctionNode f) {
@@ -392,26 +391,13 @@ module API {
MkDef(DataFlow::Node nd) { rhs(_, _, nd) } or
MkUse(DataFlow::Node nd) { use(_, _, nd) } or
/**
* A TypeScript canonical name that is defined somewhere, and that isn't a module root.
* (Module roots are represented by `MkModuleExport` nodes instead.)
*
* For most purposes, you probably want to use the `mkCanonicalNameDef` predicate instead of
* this constructor.
* A TypeScript type, identified by name of the type-annotation.
* This API node is exclusively used by `API::Node::ofType`.
*/
MkCanonicalNameDef(CanonicalName n) {
not n.isRoot() and
isDefined(n)
} or
/**
* A TypeScript canonical name that is used somewhere, and that isn't a module root.
* (Module roots are represented by `MkModuleImport` nodes instead.)
*
* For most purposes, you probably want to use the `mkCanonicalNameUse` predicate instead of
* this constructor.
*/
MkCanonicalNameUse(CanonicalName n) {
not n.isRoot() and
isUsed(n)
MkHasUnderlyingType(string moduleName, string exportName) {
any(TypeAnnotation n).hasQualifiedName(moduleName, exportName)
or
any(Type t).hasUnderlyingType(moduleName, exportName)
} or
MkSyntheticCallbackArg(DataFlow::Node src, int bound, DataFlow::InvokeNode nd) {
trackUseNode(src, true, bound).flowsTo(nd.getCalleeNode())
@@ -420,10 +406,9 @@ module API {
class TDef = MkModuleDef or TNonModuleDef;
class TNonModuleDef =
MkModuleExport or MkClassInstance or MkAsyncFuncResult or MkDef or MkCanonicalNameDef or
MkSyntheticCallbackArg;
MkModuleExport or MkClassInstance or MkAsyncFuncResult or MkDef or MkSyntheticCallbackArg;
class TUse = MkModuleUse or MkModuleImport or MkUse or MkCanonicalNameUse;
class TUse = MkModuleUse or MkModuleImport or MkUse or MkHasUnderlyingType;
private predicate hasSemantics(DataFlow::Node nd) { not nd.getTopLevel().isExterns() }
@@ -460,20 +445,6 @@ module API {
)
}
/** An API-graph node representing definitions of the canonical name `cn`. */
private TApiNode mkCanonicalNameDef(CanonicalName cn) {
if cn.isModuleRoot()
then result = MkModuleExport(cn.getExternalModuleName())
else result = MkCanonicalNameDef(cn)
}
/** An API-graph node representing uses of the canonical name `cn`. */
private TApiNode mkCanonicalNameUse(CanonicalName cn) {
if cn.isModuleRoot()
then result = MkModuleImport(cn.getExternalModuleName())
else result = MkCanonicalNameUse(cn)
}
/**
* Holds if `rhs` is the right-hand side of a definition of a node that should have an
* incoming edge from `base` labeled `lbl` in the API graph.
@@ -577,11 +548,6 @@ module API {
exists(string m | nd = MkModuleExport(m) | exports(m, rhs))
or
nd = MkDef(rhs)
or
exists(CanonicalName n | nd = MkCanonicalNameDef(n) |
rhs = n.(Namespace).getADefinition().flow() or
rhs = n.(CanonicalFunctionName).getADefinition().flow()
)
}
/**
@@ -633,10 +599,10 @@ module API {
ref = cls.getConstructor().getParameter(i)
)
or
exists(TypeName tn |
base = MkCanonicalNameUse(tn) and
exists(string moduleName, string exportName |
base = MkHasUnderlyingType(moduleName, exportName) and
lbl = Label::instance() and
ref = getANodeWithType(tn)
ref.(DataFlow::SourceNode).hasUnderlyingType(moduleName, exportName)
)
or
exists(DataFlow::InvokeNode call |
@@ -653,11 +619,11 @@ module API {
cached
predicate use(TApiNode nd, DataFlow::Node ref) {
exists(string m, Module mod | nd = MkModuleDef(m) and mod = importableModule(m) |
ref.(ModuleAsSourceNode).getModule() = mod
ref.(ModuleVarNode).getModule() = mod
)
or
exists(string m, Module mod | nd = MkModuleExport(m) and mod = importableModule(m) |
ref.(ExportsAsSourceNode).getModule() = mod
ref.(ExportsVarNode).getModule() = mod
or
exists(DataFlow::Node base | use(MkModuleDef(m), base) |
ref = trackUseNode(base).getAPropertyRead("exports")
@@ -676,8 +642,6 @@ module API {
)
or
nd = MkUse(ref)
or
exists(CanonicalName n | nd = MkCanonicalNameUse(n) | ref.asExpr() = n.getAnAccess())
}
/** Holds if module `m` exports `rhs`. */
@@ -782,9 +746,9 @@ module API {
or
// additional backwards step from `require('m')` to `exports` or `module.exports` in m
exists(Import imp | imp.getImportedModuleNode() = trackDefNode(nd, t.continue()) |
result.(ExportsAsSourceNode).getModule() = imp.getImportedModule()
result.(ExportsVarNode).getModule() = imp.getImportedModule()
or
exists(ModuleAsSourceNode mod |
exists(ModuleVarNode mod |
mod.getModule() = imp.getImportedModule() and
result = mod.(DataFlow::SourceNode).getAPropertyRead("exports")
)
@@ -832,13 +796,6 @@ module API {
result = awaited(call, DataFlow::TypeTracker::end())
}
private DataFlow::SourceNode getANodeWithType(TypeName tn) {
exists(string moduleName, string typeName |
tn.hasQualifiedName(moduleName, typeName) and
result.hasUnderlyingType(moduleName, typeName)
)
}
/**
* Holds if there is an edge from `pred` to `succ` in the API graph that is labeled with `lbl`.
*/
@@ -879,11 +836,10 @@ module API {
succ = MkClassInstance(trackDefNode(def))
)
or
exists(CanonicalName cn1, string n, CanonicalName cn2 |
pred in [mkCanonicalNameDef(cn1), mkCanonicalNameUse(cn1)] and
cn2 = cn1.getChild(n) and
lbl = Label::member(n) and
succ in [mkCanonicalNameDef(cn2), mkCanonicalNameUse(cn2)]
exists(string moduleName, string exportName |
pred = MkModuleImport(moduleName) and
lbl = Label::member(exportName) and
succ = MkHasUnderlyingType(moduleName, exportName)
)
or
exists(DataFlow::Node nd, DataFlow::FunctionNode f |
@@ -1027,31 +983,44 @@ private module Label {
string promised() { result = "promised" }
}
private class NodeModuleSourcesNodes extends DataFlow::SourceNode::Range {
Variable v;
NodeModuleSourcesNodes() {
exists(NodeModule m |
this = DataFlow::ssaDefinitionNode(SSA::implicitInit(v)) and
v = [m.getModuleVariable(), m.getExportsVariable()]
)
}
Variable getVariable() { result = v }
}
/**
* A CommonJS/AMD `module` variable, considered as a source node.
* A CommonJS/AMD `module` variable.
*/
private class ModuleAsSourceNode extends DataFlow::SourceNode::Range {
private class ModuleVarNode extends DataFlow::Node {
Module m;
ModuleAsSourceNode() {
this = DataFlow::ssaDefinitionNode(SSA::implicitInit(m.(NodeModule).getModuleVariable()))
ModuleVarNode() {
this.(NodeModuleSourcesNodes).getVariable() = m.(NodeModule).getModuleVariable()
or
this = DataFlow::parameterNode(m.(AmdModule).getDefine().getModuleParameter())
DataFlow::parameterNode(this, m.(AmdModule).getDefine().getModuleParameter())
}
Module getModule() { result = m }
}
/**
* A CommonJS/AMD `exports` variable, considered as a source node.
* A CommonJS/AMD `exports` variable.
*/
private class ExportsAsSourceNode extends DataFlow::SourceNode::Range {
private class ExportsVarNode extends DataFlow::Node {
Module m;
ExportsAsSourceNode() {
this = DataFlow::ssaDefinitionNode(SSA::implicitInit(m.(NodeModule).getExportsVariable()))
ExportsVarNode() {
this.(NodeModuleSourcesNodes).getVariable() = m.(NodeModule).getExportsVariable()
or
this = DataFlow::parameterNode(m.(AmdModule).getDefine().getExportsParameter())
DataFlow::parameterNode(this, m.(AmdModule).getDefine().getExportsParameter())
}
Module getModule() { result = m }

View File

@@ -84,16 +84,17 @@ private module ArrayDataFlow {
* A step modelling 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::AdditionalFlowStep, DataFlow::CallNode {
ArrayFrom() { this = DataFlow::globalVarRef("Array").getAMemberCall("from") }
private class ArrayFrom extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
) {
pred = this.getArgument(0) and
succ = this and
fromProp = arrayLikeElement() and
toProp = arrayElement()
exists(DataFlow::CallNode call |
call = DataFlow::globalVarRef("Array").getAMemberCall("from") and
pred = call.getArgument(0) and
succ = call and
fromProp = arrayLikeElement() and
toProp = arrayElement()
)
}
}
@@ -103,55 +104,45 @@ 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::AdditionalFlowStep {
DataFlow::Node spreadArgument; // the spread argument containing the elements to be copied.
DataFlow::Node base; // the object where the elements should be copied to.
ArrayCopySpread() {
exists(DataFlow::MethodCallNode mcn | mcn = this |
mcn.getMethodName() = ["push", "unshift"] and
spreadArgument = mcn.getASpreadArgument() and
base = mcn.getReceiver().getALocalSource()
)
or
spreadArgument = this.(DataFlow::ArrayCreationNode).getASpreadArgument() and
base = this
}
private class ArrayCopySpread extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
) {
pred = spreadArgument and
succ = base and
fromProp = arrayLikeElement() and
toProp = arrayElement()
toProp = arrayElement() and
(
exists(DataFlow::MethodCallNode mcn |
mcn.getMethodName() = ["push", "unshift"] and
pred = mcn.getASpreadArgument() and
succ = mcn.getReceiver().getALocalSource()
)
or
pred = succ.(DataFlow::ArrayCreationNode).getASpreadArgument()
)
}
}
/**
* A step for storing an element on an array using `arr.push(e)` or `arr.unshift(e)`.
*/
private class ArrayAppendStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayAppendStep() {
this.getMethodName() = "push" or
this.getMethodName() = "unshift"
}
private class ArrayAppendStep extends DataFlow::SharedFlowStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
prop = arrayElement() and
element = this.getAnArgument() and
obj.getAMethodCall() = this
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["push", "unshift"] and
element = call.getAnArgument() and
obj.getAMethodCall() = call
)
}
}
/**
* 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]}`.
* A node that reads or writes an element from an array inside a for-loop.
*/
private class ArrayIndexingStep extends DataFlow::AdditionalFlowStep, DataFlow::Node {
private class ArrayIndexingAccess extends DataFlow::Node {
DataFlow::PropRef read;
ArrayIndexingStep() {
ArrayIndexingAccess() {
read = this and
TTNumber() =
unique(InferredType type | type = read.getPropertyNameExpr().flow().analyze().getAType()) and
@@ -162,17 +153,27 @@ private module ArrayDataFlow {
i.getVariable().getADefinition().(VariableDeclarator).getDeclStmt() = init
)
}
}
/**
* 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 {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
prop = arrayElement() and
obj = this.(DataFlow::PropRead).getBase() and
element = this
exists(ArrayIndexingAccess access |
prop = arrayElement() and
obj = access.(DataFlow::PropRead).getBase() and
element = access
)
}
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
prop = arrayElement() and
element = this.(DataFlow::PropWrite).getRhs() and
this = obj.getAPropertyWrite()
exists(ArrayIndexingAccess access |
prop = arrayElement() and
element = access.(DataFlow::PropWrite).getRhs() and
access = obj.getAPropertyWrite()
)
}
}
@@ -180,16 +181,14 @@ private module ArrayDataFlow {
* A step for retrieving an element from an array using `.pop()` or `.shift()`.
* E.g. `array.pop()`.
*/
private class ArrayPopStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayPopStep() {
getMethodName() = "pop" or
getMethodName() = "shift"
}
private class ArrayPopStep extends DataFlow::SharedFlowStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
prop = arrayElement() and
obj = this.getReceiver() and
element = this
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["pop", "shift"] and
prop = arrayElement() and
obj = call.getReceiver() and
element = call
)
}
}
@@ -235,12 +234,12 @@ private module ArrayDataFlow {
/**
* A step for creating an array and storing the elements in the array.
*/
private class ArrayCreationStep extends DataFlow::AdditionalFlowStep, DataFlow::ArrayCreationNode {
private class ArrayCreationStep extends DataFlow::SharedFlowStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(int i |
element = this.getElement(i) and
obj = this and
if this = any(PromiseAllCreation c).getArrayNode()
exists(DataFlow::ArrayCreationNode array, int i |
element = array.getElement(i) and
obj = array and
if array = any(PromiseAllCreation c).getArrayNode()
then prop = arrayElement(i)
else prop = arrayElement()
)
@@ -251,13 +250,14 @@ private module ArrayDataFlow {
* A step modelling 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::AdditionalFlowStep, DataFlow::MethodCallNode {
ArraySpliceStep() { this.getMethodName() = "splice" }
private class ArraySpliceStep extends DataFlow::SharedFlowStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
prop = arrayElement() and
element = getArgument(2) and
this = obj.getAMethodCall()
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "splice" and
prop = arrayElement() and
element = call.getArgument(2) and
call = obj.getAMethodCall()
)
}
}
@@ -265,42 +265,27 @@ private module ArrayDataFlow {
* A step for modelling `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::AdditionalFlowStep, DataFlow::MethodCallNode {
ArrayConcatStep() { this.getMethodName() = "concat" }
private class ArrayConcatStep extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = arrayElement() and
(pred = this.getReceiver() or pred = this.getAnArgument()) and
succ = this
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "concat" and
prop = arrayElement() and
(pred = call.getReceiver() or pred = call.getAnArgument()) and
succ = call
)
}
}
/**
* A step for modelling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`.
*/
private class ArraySliceStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
ArraySliceStep() {
this.getMethodName() = "slice" or
this.getMethodName() = "splice" or
this.getMethodName() = "filter"
}
private class ArraySliceStep extends DataFlow::SharedFlowStep {
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
prop = arrayElement() and
pred = this.getReceiver() and
succ = this
}
}
/**
* A step for modelling `for of` iteration on arrays.
*/
private class ForOfStep extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node e, string prop) {
exists(ForOfStmt forOf |
obj = forOf.getIterationDomain().flow() and
e = DataFlow::lvalueNode(forOf.getLValue()) and
prop = arrayElement()
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["slice", "splice", "filter"] and
prop = arrayElement() and
pred = call.getReceiver() and
succ = call
)
}
}

View File

@@ -6,32 +6,30 @@
import javascript
private import semmle.javascript.dataflow.internal.StepSummary
private import semmle.javascript.dataflow.internal.PreCallGraphStep
private import DataFlow::PseudoProperties
/**
* A pseudo-property used in a data-flow/type-tracking step for collections.
* DEPRECATED. Exists only to support other deprecated elements.
*
* By extending `TypeTrackingPseudoProperty` the class enables the use of the collection related pseudo-properties in type-tracking predicates.
* Type-tracking now automatically determines the set of pseudo-properties to include
* ased on which properties are contributed by `SharedTaintStep`s.
*/
private class PseudoProperty extends TypeTrackingPseudoProperty {
deprecated private class PseudoProperty extends string {
PseudoProperty() {
this = [arrayLikeElement(), "1"] or // the "1" is required for the `ForOfStep`.
this = any(CollectionDataFlow::MapSet step).getAPseudoProperty()
}
override PseudoProperty getLoadStoreToProp() {
exists(CollectionFlowStep step | step.loadStore(_, _, this, result))
this =
[
mapValue(any(DataFlow::CallNode c | c.getCalleeName() = "set").getArgument(0)),
mapValueAll()
]
}
}
/**
* An `AdditionalFlowStep` used to model a data-flow step related to standard library collections.
*
* The `loadStep`/`storeStep`/`loadStoreStep` methods are overloaded such that the new predicates
* `load`/`store`/`loadStore` can be used in the `CollectionsTypeTracking` module.
* (Thereby avoiding naming conflicts with a "cousin" `AdditionalFlowStep` implementation.)
* DEPRECATED. Use `SharedFlowStep` or `SharedTaintTrackingStep` instead.
*/
abstract class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
abstract deprecated class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
final override predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
final override predicate step(
@@ -84,27 +82,28 @@ abstract class CollectionFlowStep extends DataFlow::AdditionalFlowStep {
}
/**
* Provides predicates and clases for type-tracking collections.
* DEPRECATED. These steps are now included in the default type tracking steps,
* in most cases one can simply use those instead.
*/
module CollectionsTypeTracking {
deprecated module CollectionsTypeTracking {
/**
* Gets the result from a single step through a collection, from `pred` to `result` summarized by `summary`.
*/
pragma[inline]
DataFlow::SourceNode collectionStep(DataFlow::Node pred, StepSummary summary) {
exists(CollectionFlowStep step, PseudoProperty field |
exists(PseudoProperty field |
summary = LoadStep(field) and
step.load(pred, result, field) and
DataFlow::SharedTypeTrackingStep::loadStep(pred, result, field) and
not field = mapValueUnknownKey() // prune unknown reads in type-tracking
or
summary = StoreStep(field) and
step.store(pred, result, field)
DataFlow::SharedTypeTrackingStep::storeStep(pred, result, field)
or
summary = CopyStep(field) and
step.loadStore(pred, result, field)
DataFlow::SharedTypeTrackingStep::loadStoreStep(pred, result, field)
or
exists(PseudoProperty toField | summary = LoadStoreStep(field, toField) |
step.loadStore(pred, result, field, toField)
DataFlow::SharedTypeTrackingStep::loadStoreStep(pred, result, field, toField)
)
)
}
@@ -129,74 +128,71 @@ private module CollectionDataFlow {
/**
* A step for `Set.add()` method, which adds an element to a Set.
*/
private class SetAdd extends CollectionFlowStep, DataFlow::MethodCallNode {
SetAdd() { this.getMethodName() = "add" }
override predicate store(DataFlow::Node element, DataFlow::SourceNode obj, PseudoProperty prop) {
this = obj.getAMethodCall() and
element = this.getArgument(0) and
prop = setElement()
private class SetAdd extends PreCallGraphStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(DataFlow::MethodCallNode call |
call = obj.getAMethodCall("add") and
element = call.getArgument(0) and
prop = setElement()
)
}
}
/**
* A step for the `Set` constructor, which copies any elements from the first argument into the resulting set.
*/
private class SetConstructor extends CollectionFlowStep, DataFlow::NewNode {
SetConstructor() { this = DataFlow::globalVarRef("Set").getAnInstantiation() }
override predicate loadStore(
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
private class SetConstructor extends PreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
pred = this.getArgument(0) and
succ = this and
fromProp = arrayLikeElement() and
toProp = setElement()
exists(DataFlow::NewNode invoke |
invoke = DataFlow::globalVarRef("Set").getAnInstantiation() and
pred = invoke.getArgument(0) and
succ = invoke and
fromProp = arrayLikeElement() and
toProp = setElement()
)
}
}
/**
* A step for a `for of` statement on a Map, Set, or Iterator.
* For Sets and iterators the l-value are the elements of the set/iterator.
* For Maps the l-value is a tuple containing a key and a value.
* A step for modelling `for of` iteration on arrays, maps, sets, and iterators.
*
* For sets and iterators the l-value are the elements of the set/iterator.
* For maps the l-value is a tuple containing a key and a value.
*/
// This is partially duplicated behavior with the `for of` step for Arrays (`ArrayDataFlow::ForOfStep`).
// This duplication is required for the type-tracking steps defined in `CollectionsTypeTracking`.
private class ForOfStep extends CollectionFlowStep, DataFlow::ValueNode {
ForOfStmt forOf;
DataFlow::Node element;
ForOfStep() {
this.asExpr() = forOf.getIterationDomain() and
element = DataFlow::lvalueNode(forOf.getLValue())
private class ForOfStep extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node e, string prop) {
exists(ForOfStmt forOf |
obj = forOf.getIterationDomain().flow() and
e = DataFlow::lvalueNode(forOf.getLValue()) and
prop = arrayLikeElement()
)
}
override predicate load(DataFlow::Node obj, DataFlow::Node e, PseudoProperty prop) {
obj = this and
e = element and
prop = arrayLikeElement()
}
override predicate loadStore(
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
pred = this and
succ = element and
fromProp = mapValueAll() and
toProp = "1"
exists(ForOfStmt forOf |
pred = forOf.getIterationDomain().flow() and
succ = DataFlow::lvalueNode(forOf.getLValue()) and
fromProp = mapValueAll() and
toProp = "1"
)
}
}
/**
* A step for a call to `forEach` on a Set or Map.
*/
private class SetMapForEach extends CollectionFlowStep, DataFlow::MethodCallNode {
SetMapForEach() { this.getMethodName() = "forEach" }
override predicate load(DataFlow::Node obj, DataFlow::Node element, PseudoProperty prop) {
obj = this.getReceiver() and
element = this.getCallback(0).getParameter(0) and
prop = [setElement(), mapValueAll()]
private class SetMapForEach extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "forEach" and
obj = call.getReceiver() and
element = call.getCallback(0).getParameter(0) and
prop = [setElement(), mapValueAll()]
)
}
}
@@ -204,14 +200,15 @@ private module CollectionDataFlow {
* A call to the `get` method on a Map.
* If the key of the call to `get` has a known string value, then only the value corresponding to that key will be retrieved. (The known string value is encoded as part of the pseudo-property)
*/
private class MapGet extends CollectionFlowStep, DataFlow::MethodCallNode {
MapGet() { this.getMethodName() = "get" }
override predicate load(DataFlow::Node obj, DataFlow::Node element, PseudoProperty prop) {
obj = this.getReceiver() and
element = this and
// reading the join of known and unknown values
(prop = mapValue(this.getArgument(0)) or prop = mapValueUnknownKey())
private class MapGet extends PreCallGraphStep {
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "get" and
obj = call.getReceiver() and
element = call and
// reading the join of known and unknown values
(prop = mapValue(call.getArgument(0)) or prop = mapValueUnknownKey())
)
}
}
@@ -223,56 +220,47 @@ private module CollectionDataFlow {
* Otherwise the value will be stored into a pseudo-property corresponding to values with unknown keys.
* The value will additionally be stored into a pseudo-property corresponding to all values.
*/
class MapSet extends CollectionFlowStep, DataFlow::MethodCallNode {
MapSet() { this.getMethodName() = "set" }
override predicate store(DataFlow::Node element, DataFlow::SourceNode obj, PseudoProperty prop) {
this = obj.getAMethodCall() and
element = this.getArgument(1) and
prop = getAPseudoProperty()
class MapSet extends PreCallGraphStep {
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
exists(DataFlow::MethodCallNode call |
call = obj.getAMethodCall("set") and
element = call.getArgument(1) and
prop = [mapValue(call.getArgument(0)), mapValueAll()]
)
}
/**
* Gets a pseudo-property used to store an element in a map.
* The pseudo-property represents both values where the key is a known string value (which is encoded in the pseudo-property),
* and values where the key is unknown.
*
* Additionally, all elements are stored into the pseudo-property `mapValueAll()`.
*
* The return-type is `string` as this predicate is used to define which pseudo-properties exist.
*/
string getAPseudoProperty() { result = [mapValue(this.getArgument(0)), mapValueAll()] }
}
/**
* A step for a call to `values` on a Map or a Set.
*/
private class MapAndSetValues extends CollectionFlowStep, DataFlow::MethodCallNode {
MapAndSetValues() { this.getMethodName() = "values" }
override predicate loadStore(
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
private class MapAndSetValues extends PreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
pred = this.getReceiver() and
succ = this and
fromProp = [mapValueAll(), setElement()] and
toProp = iteratorElement()
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "values" and
pred = call.getReceiver() and
succ = call and
fromProp = [mapValueAll(), setElement()] and
toProp = iteratorElement()
)
}
}
/**
* A step for a call to `keys` on a Set.
*/
private class SetKeys extends CollectionFlowStep, DataFlow::MethodCallNode {
SetKeys() { this.getMethodName() = "keys" }
override predicate loadStore(
DataFlow::Node pred, DataFlow::Node succ, PseudoProperty fromProp, PseudoProperty toProp
private class SetKeys extends PreCallGraphStep {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string fromProp, string toProp
) {
pred = this.getReceiver() and
succ = this and
fromProp = setElement() and
toProp = iteratorElement()
exists(DataFlow::MethodCallNode call |
call.getMethodName() = "keys" and
pred = call.getReceiver() and
succ = call and
fromProp = setElement() and
toProp = iteratorElement()
)
}
}
}

View File

@@ -315,6 +315,11 @@ module DOM {
)
}
private InferredType getArgumentTypeFromJQueryMethodGet(JQuery::MethodCall call) {
call.getMethodName() = "get" and
result = call.getArgument(0).analyze().getAType()
}
private class DefaultRange extends Range {
DefaultRange() {
this.asExpr().(VarAccess).getVariable() instanceof DOMGlobalVariable
@@ -344,7 +349,7 @@ module DOM {
or
exists(JQuery::MethodCall call | this = call and call.getMethodName() = "get" |
call.getNumArgument() = 1 and
unique(InferredType t | t = call.getArgument(0).analyze().getAType()) = TTNumber()
unique(InferredType t | t = getArgumentTypeFromJQueryMethodGet(call)) = TTNumber()
)
or
// A `this` node from a callback given to a `$().each(callback)` call.

View File

@@ -305,6 +305,7 @@ module AccessPath {
* }
* ```
*/
pragma[inline]
DataFlow::Node getAReferenceTo(Root root, string path) {
path = fromReference(result, root) and
not root.isGlobal()
@@ -327,6 +328,7 @@ module AccessPath {
* })(NS = NS || {});
* ```
*/
pragma[inline]
DataFlow::Node getAReferenceTo(string path) {
path = fromReference(result, DataFlow::globalAccessPathRootPseudoNode())
}
@@ -347,6 +349,7 @@ module AccessPath {
* }
* ```
*/
pragma[inline]
DataFlow::Node getAnAssignmentTo(Root root, string path) {
path = fromRhs(result, root) and
not root.isGlobal()
@@ -367,6 +370,7 @@ module AccessPath {
* })(foo = foo || {});
* ```
*/
pragma[inline]
DataFlow::Node getAnAssignmentTo(string path) {
path = fromRhs(result, DataFlow::globalAccessPathRootPseudoNode())
}
@@ -376,6 +380,7 @@ module AccessPath {
*
* See `getAReferenceTo` and `getAnAssignmentTo` for more details.
*/
pragma[inline]
DataFlow::Node getAReferenceOrAssignmentTo(string path) {
result = getAReferenceTo(path)
or
@@ -387,6 +392,7 @@ module AccessPath {
*
* See `getAReferenceTo` and `getAnAssignmentTo` for more details.
*/
pragma[inline]
DataFlow::Node getAReferenceOrAssignmentTo(Root root, string path) {
result = getAReferenceTo(root, path)
or

View File

@@ -16,51 +16,53 @@ abstract class HtmlSanitizerCall extends DataFlow::CallNode {
abstract DataFlow::Node getInput();
}
pragma[noinline]
private DataFlow::SourceNode htmlSanitizerFunction() {
result = DataFlow::moduleMember("ent", "encode")
or
result = DataFlow::moduleMember("entities", "encodeHTML")
or
result = DataFlow::moduleMember("entities", "encodeXML")
or
result = DataFlow::moduleMember("escape-goat", "escape")
or
result = DataFlow::moduleMember("he", "encode")
or
result = DataFlow::moduleMember("he", "escape")
or
result = DataFlow::moduleImport("sanitize-html")
or
result = DataFlow::moduleMember("sanitizer", "escape")
or
result = DataFlow::moduleMember("sanitizer", "sanitize")
or
result = DataFlow::moduleMember("validator", "escape")
or
result = DataFlow::moduleImport("xss")
or
result = DataFlow::moduleMember("xss-filters", _)
or
result = LodashUnderscore::member("escape")
or
exists(DataFlow::PropRead read | read = result |
read.getPropertyName() = "sanitize" and
read.getBase().asExpr().(VarAccess).getName() = "DOMPurify"
)
or
exists(string name | name = "encode" or name = "encodeNonUTF" |
result = DataFlow::moduleMember("html-entities", _).getAnInstantiation().getAPropertyRead(name) or
result = DataFlow::moduleMember("html-entities", _).getAPropertyRead(name)
)
or
result = Closure::moduleImport("goog.string.htmlEscape")
}
/**
* Matches HTML sanitizers from known NPM packages as well as home-made sanitizers (matched by name).
*/
private class DefaultHtmlSanitizerCall extends HtmlSanitizerCall {
DefaultHtmlSanitizerCall() {
exists(DataFlow::SourceNode callee | this = callee.getACall() |
callee = DataFlow::moduleMember("ent", "encode")
or
callee = DataFlow::moduleMember("entities", "encodeHTML")
or
callee = DataFlow::moduleMember("entities", "encodeXML")
or
callee = DataFlow::moduleMember("escape-goat", "escape")
or
callee = DataFlow::moduleMember("he", "encode")
or
callee = DataFlow::moduleMember("he", "escape")
or
callee = DataFlow::moduleImport("sanitize-html")
or
callee = DataFlow::moduleMember("sanitizer", "escape")
or
callee = DataFlow::moduleMember("sanitizer", "sanitize")
or
callee = DataFlow::moduleMember("validator", "escape")
or
callee = DataFlow::moduleImport("xss")
or
callee = DataFlow::moduleMember("xss-filters", _)
or
callee = LodashUnderscore::member("escape")
or
exists(DataFlow::PropRead read | read = callee |
read.getPropertyName() = "sanitize" and
read.getBase().asExpr().(VarAccess).getName() = "DOMPurify"
)
or
exists(string name | name = "encode" or name = "encodeNonUTF" |
callee =
DataFlow::moduleMember("html-entities", _).getAnInstantiation().getAPropertyRead(name) or
callee = DataFlow::moduleMember("html-entities", _).getAPropertyRead(name)
)
or
callee = Closure::moduleImport("goog.string.htmlEscape")
)
this = htmlSanitizerFunction().getACall()
or
// Match home-made sanitizers by name.
exists(string calleeName | calleeName = getCalleeName() |

View File

@@ -70,6 +70,58 @@ private DataFlow::Node getAValueExportedByPackage() {
result = cla.getAStaticMethod() or
result = cla.getConstructor()
)
or
// *****
// Common styles of transforming exported objects.
// *****
//
// Object.defineProperties
exists(DataFlow::MethodCallNode call |
call = DataFlow::globalVarRef("Object").getAMethodCall("defineProperties") and
[call, call.getArgument(0)] = getAValueExportedByPackage() and
result = call.getArgument(any(int i | i > 0))
)
or
// Object.defineProperty
exists(CallToObjectDefineProperty call |
[call, call.getBaseObject()] = getAValueExportedByPackage()
|
result = call.getPropertyDescriptor().getALocalSource().getAPropertyReference("value")
or
result =
call.getPropertyDescriptor()
.getALocalSource()
.getAPropertyReference("get")
.(DataFlow::FunctionNode)
.getAReturn()
)
or
// Object.assign and friends
exists(ExtendCall assign |
getAValueExportedByPackage() = [assign, assign.getDestinationOperand()] and
result = assign.getASourceOperand()
)
or
// Array.prototype.{map, reduce, entries, values}
exists(DataFlow::MethodCallNode map |
map.getMethodName() = ["map", "reduce", "entries", "values"] and
map = getAValueExportedByPackage()
|
result = map.getArgument(0).getABoundFunctionValue(_).getAReturn()
or
// assuming that the receiver of the call is somehow exported
result = map.getReceiver()
)
or
// Object.{fromEntries, freeze, seal, entries, values}
exists(DataFlow::MethodCallNode freeze |
freeze =
DataFlow::globalVarRef("Object")
.getAMethodCall(["fromEntries", "freeze", "seal", "entries", "values"])
|
freeze = getAValueExportedByPackage() and
result = freeze.getArgument(0)
)
}
/**

View File

@@ -141,29 +141,7 @@ abstract class PathString extends string {
* components of this path refers to when resolved relative to the
* given `root` folder.
*/
Path resolveUpTo(int n, Folder root) {
n = 0 and result.getContainer() = root and root = getARootFolder()
or
exists(Path base, string next | next = getComponent(this, n - 1, base, root) |
// handle empty components and the special "." folder
(next = "" or next = ".") and
result = base
or
// handle the special ".." folder
next = ".." and result = base.(ConsPath).getParent()
or
// special handling for Windows drive letters when resolving absolute path:
// the extractor populates "C:/" as a folder that has path "C:/" but name ""
n = 1 and
next.regexpMatch("[A-Za-z]:") and
root.getBaseName() = "" and
root.toString() = next.toUpperCase() + "/" and
result = base
or
// default case
result = TConsPath(base, next)
)
}
Path resolveUpTo(int n, Folder root) { result = resolveUpTo(this, n, root, _) }
/**
* Gets the absolute path that this path refers to when resolved relative to
@@ -172,16 +150,57 @@ abstract class PathString extends string {
Path resolve(Folder root) { result = resolveUpTo(getNumComponent(), root) }
}
/**
* Gets the absolute path that the sub-path consisting of the first `n`
* components of this path refers to when resolved relative to the
* given `root` folder.
*/
private Path resolveUpTo(PathString p, int n, Folder root, boolean inTS) {
n = 0 and result.getContainer() = root and root = p.getARootFolder() and inTS = false
or
exists(Path base, string next | next = getComponent(p, n - 1, base, root, inTS) |
// handle empty components and the special "." folder
(next = "" or next = ".") and
result = base
or
// handle the special ".." folder
next = ".." and result = base.(ConsPath).getParent()
or
// special handling for Windows drive letters when resolving absolute path:
// the extractor populates "C:/" as a folder that has path "C:/" but name ""
n = 1 and
next.regexpMatch("[A-Za-z]:") and
root.getBaseName() = "" and
root.toString() = next.toUpperCase() + "/" and
result = base
or
// default case
result = TConsPath(base, next)
)
}
/**
* Gets the `i`th component of the path `str`, where `base` is the resolved path one level up.
* Supports that the root directory might be compiled output from TypeScript.
* `inTS` is true if the result is TypeScript that is compiled into the path specified by `str`.
*/
private string getComponent(PathString str, int n, Path base, Folder root) {
base = str.resolveUpTo(n, root) and
(
result = str.getComponent(n)
or
result = TypeScriptOutDir::getOriginalTypeScriptFolder(str.getComponent(n), base.getContainer())
private string getComponent(PathString str, int n, Path base, Folder root, boolean inTS) {
exists(boolean prevTS |
base = resolveUpTo(str, n, root, prevTS) and
(
result = str.getComponent(n) and prevTS = inTS
or
// If we are in a TypeScript source folder, try to replace file endings with ".ts" or ".tsx"
n = str.getNumComponent() - 1 and
prevTS = true and
inTS = prevTS and
result = str.getComponent(n).regexpCapture("^(.*)\\.js$", 1) + "." + ["ts", "tsx"]
or
prevTS = false and
inTS = true and
result =
TypeScriptOutDir::getOriginalTypeScriptFolder(str.getComponent(n), base.getContainer())
)
)
}
@@ -194,21 +213,35 @@ private module TypeScriptOutDir {
*/
string getOriginalTypeScriptFolder(string outdir, Folder parent) {
exists(JSONObject tsconfig |
tsconfig.getFile().getBaseName() = "tsconfig.json" and
tsconfig.isTopLevel() and
tsconfig.getFile().getParentContainer() = parent
|
outdir =
tsconfig
.getPropValue("compilerOptions")
.(JSONObject)
.getPropValue("outDir")
.(JSONString)
.getValue() and
result = getEffectiveRootDirFromTSConfig(tsconfig)
outdir = removeLeadingSlash(getOutDir(tsconfig, parent)) and
result = removeLeadingSlash(getEffectiveRootDirFromTSConfig(tsconfig))
)
}
/**
* Removes a leading dot and/or slash from `raw`.
*/
bindingset[raw]
private string removeLeadingSlash(string raw) {
result = raw.regexpCapture("^\\.?/?([\\w.\\-]+)$", 1)
}
/**
* Gets the `outDir` option from a tsconfig file from the folder `parent`.
*/
private string getOutDir(JSONObject tsconfig, Folder parent) {
tsconfig.getFile().getBaseName().regexpMatch("tsconfig.*\\.json") and
tsconfig.isTopLevel() and
tsconfig.getFile().getParentContainer() = parent and
result =
tsconfig
.getPropValue("compilerOptions")
.(JSONObject)
.getPropValue("outDir")
.(JSONString)
.getValue()
}
/**
* Gets the directory that contains the TypeScript source files.
* Based on the tsconfig.json file `tsconfig`.

View File

@@ -214,13 +214,6 @@ module PromiseTypeTracking {
result = PromiseTypeTracking::promiseStep(mid, summary)
)
}
/**
* A class enabling the use of the `resolveField` as a pseudo-property in type-tracking predicates.
*/
private class ResolveFieldAsTypeTrackingProperty extends TypeTrackingPseudoProperty {
ResolveFieldAsTypeTrackingProperty() { this = Promises::valueProp() }
}
}
private import semmle.javascript.dataflow.internal.PreCallGraphStep
@@ -596,6 +589,7 @@ private module ClosurePromise {
* A promise created by a call `goog.Promise.resolve(value)`.
*/
private class ResolvedClosurePromiseDefinition extends ResolvedPromiseDefinition {
pragma[noinline]
ResolvedClosurePromiseDefinition() {
this = Closure::moduleImport("goog.Promise.resolve").getACall()
}

View File

@@ -74,23 +74,13 @@ private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInv
* A flow step propagating the exception thrown from a callback to a method whose name coincides
* a built-in Array iteration method, such as `forEach` or `map`.
*/
private class IteratorExceptionStep extends DataFlow::MethodCallNode, DataFlow::AdditionalFlowStep {
IteratorExceptionStep() {
exists(string name | name = getMethodName() |
name = "forEach" or
name = "each" or
name = "map" or
name = "filter" or
name = "some" or
name = "every" or
name = "fold" or
name = "reduce"
)
}
private class IteratorExceptionStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
succ = this.getExceptionalReturn()
exists(DataFlow::MethodCallNode call |
call.getMethodName() = ["forEach", "each", "map", "filter", "some", "every", "fold", "reduce"] and
pred = call.getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
succ = call.getExceptionalReturn()
)
}
}

View File

@@ -55,12 +55,8 @@ module StringConcatenation {
exists(DataFlow::MethodCallNode call |
node = call and
call.getMethodName() = "concat" and
not (
exists(DataFlow::ArrayCreationNode array |
array.flowsTo(call.getAnArgument()) or array.flowsTo(call.getReceiver())
)
or
DataFlow::reflectiveCallNode(_) = call
not exists(DataFlow::ArrayCreationNode array |
array.flowsTo(call.getAnArgument()) or array.flowsTo(call.getReceiver())
) and
(
n = 0 and

View File

@@ -544,6 +544,12 @@ abstract class LabeledBarrierGuardNode extends BarrierGuardNode {
}
/**
* DEPRECATED. Subclasses should extend `SharedFlowStep` instead, unless the subclass
* is part of a query, in which case it should be moved into the `isAdditionalFlowStep` predicate
* of the relevant data-flow configuration.
* Other uses of the predicate in this class should instead reference the predicates in the
* `SharedFlowStep::` module, such as `SharedFlowStep::step`.
*
* A data flow edge that should be added to all data flow configurations in
* addition to standard data flow edges.
*
@@ -551,19 +557,19 @@ abstract class LabeledBarrierGuardNode extends BarrierGuardNode {
* of the standard library. Override `Configuration::isAdditionalFlowStep`
* for analysis-specific flow steps.
*/
cached
abstract class AdditionalFlowStep extends DataFlow::Node {
deprecated class AdditionalFlowStep = LegacyAdditionalFlowStep;
// Internal version of AdditionalFlowStep that we can reference without deprecation warnings.
abstract private class LegacyAdditionalFlowStep extends DataFlow::Node {
/**
* Holds if `pred` &rarr; `succ` should be considered a data flow edge.
*/
cached
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
/**
* Holds if `pred` &rarr; `succ` should be considered a data flow edge
* transforming values with label `predlbl` to have label `succlbl`.
*/
cached
predicate step(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
@@ -577,7 +583,6 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
* The object `succ` must be a `DataFlow::SourceNode` for the object wherein the value is stored.
*/
cached
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
@@ -585,7 +590,6 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
*
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
*/
cached
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
@@ -593,7 +597,6 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
*
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
*/
cached
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
@@ -601,12 +604,10 @@ abstract class AdditionalFlowStep extends DataFlow::Node {
*
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
*/
cached
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
loadProp = storeProp and
loadStoreStep(pred, succ, loadProp)
none()
}
}
@@ -636,28 +637,133 @@ class SharedFlowStep extends Unit {
) {
none()
}
/**
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
* The object `succ` must be a `DataFlow::SourceNode` for the object wherein the value is stored.
*/
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
*/
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
*/
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
none()
}
}
/**
* Contributes subclasses of `SharedFlowStep` to `AdditionalFlowStep`.
*
* This is a placeholder until we migrate to the `SharedFlowStep` class and deprecate `AdditionalFlowStep`.
* Contains predicates for accessing the steps contributed by `SharedFlowStep` subclasses.
*/
private class SharedStepAsAdditionalFlowStep extends AdditionalFlowStep {
SharedStepAsAdditionalFlowStep() {
any(SharedFlowStep st).step(_, this) or
any(SharedFlowStep st).step(_, this, _, _)
cached
module SharedFlowStep {
cached
private module Internal {
// Forces this to be part of the `FlowSteps` stage.
// We use a public predicate in a private module to avoid warnings about this being unused.
cached
predicate forceStage() { Stages::FlowSteps::ref() }
}
/**
* Holds if `pred` &rarr; `succ` should be considered a data flow edge.
*/
cached
predicate step(DataFlow::Node pred, DataFlow::Node succ) {
any(SharedFlowStep s).step(pred, succ)
}
/**
* Holds if `pred` &rarr; `succ` should be considered a data flow edge
* transforming values with label `predlbl` to have label `succlbl`.
*/
cached
predicate step(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
) {
any(SharedFlowStep s).step(pred, succ, predlbl, succlbl)
}
/**
* Holds if `pred` should be stored in the object `succ` under the property `prop`.
* The object `succ` must be a `DataFlow::SourceNode` for the object wherein the value is stored.
*/
cached
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(SharedFlowStep s).storeStep(pred, succ, prop)
}
/**
* Holds if the property `prop` of the object `pred` should be loaded into `succ`.
*/
cached
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(SharedFlowStep s).loadStep(pred, succ, prop)
}
/**
* Holds if the property `prop` should be copied from the object `pred` to the object `succ`.
*/
cached
predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(SharedFlowStep s).loadStoreStep(pred, succ, prop)
}
/**
* Holds if the property `loadProp` should be copied from the object `pred` to the property `storeProp` of object `succ`.
*/
cached
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
any(SharedFlowStep s).loadStoreStep(pred, succ, loadProp, storeProp)
}
}
/**
* Contributes subclasses of `AdditionalFlowStep` to `SharedFlowStep`.
*/
private class AdditionalFlowStepAsSharedStep extends SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
any(SharedFlowStep st).step(pred, succ) and succ = this
any(LegacyAdditionalFlowStep s).step(pred, succ)
}
override predicate step(
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel predlbl,
DataFlow::FlowLabel succlbl
) {
any(SharedFlowStep st).step(pred, succ, predlbl, succlbl) and succ = this
any(LegacyAdditionalFlowStep s).step(pred, succ, predlbl, succlbl)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(LegacyAdditionalFlowStep s).storeStep(pred, succ, prop)
}
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(LegacyAdditionalFlowStep s).loadStep(pred, succ, prop)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(LegacyAdditionalFlowStep s).loadStoreStep(pred, succ, prop)
}
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
any(LegacyAdditionalFlowStep s).loadStoreStep(pred, succ, loadProp, storeProp)
}
}
@@ -666,7 +772,7 @@ private class SharedStepAsAdditionalFlowStep extends AdditionalFlowStep {
*
* A pseudo-property represents the location where some value is stored in an object.
*
* For use with load/store steps in `DataFlow::AdditionalFlowStep` and TypeTracking.
* For use with load/store steps in `DataFlow::SharedFlowStep` and TypeTracking.
*/
module PseudoProperties {
bindingset[s]
@@ -782,13 +888,12 @@ abstract class AdditionalSink extends DataFlow::Node {
* Additional flow step to model flow from import specifiers into the SSA variable
* corresponding to the imported variable.
*/
private class FlowStepThroughImport extends AdditionalFlowStep, DataFlow::ValueNode {
override ImportSpecifier astNode;
private class FlowStepThroughImport extends SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
Stages::FlowSteps::ref() and
pred = this and
succ = DataFlow::ssaDefinitionNode(SSA::definition(astNode))
exists(ImportSpecifier specifier |
pred = DataFlow::valueNode(specifier) and
succ = DataFlow::ssaDefinitionNode(SSA::definition(specifier))
)
}
}
@@ -1202,7 +1307,7 @@ private predicate reachesReturn(
private predicate isAdditionalLoadStep(
DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg
) {
any(AdditionalFlowStep s).loadStep(pred, succ, prop)
SharedFlowStep::loadStep(pred, succ, prop)
or
cfg.isAdditionalLoadStep(pred, succ, prop)
}
@@ -1213,7 +1318,7 @@ private predicate isAdditionalLoadStep(
private predicate isAdditionalStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string prop, DataFlow::Configuration cfg
) {
any(AdditionalFlowStep s).storeStep(pred, succ, prop)
SharedFlowStep::storeStep(pred, succ, prop)
or
cfg.isAdditionalStoreStep(pred, succ, prop)
}
@@ -1225,13 +1330,13 @@ private predicate isAdditionalLoadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp,
DataFlow::Configuration cfg
) {
any(AdditionalFlowStep s).loadStoreStep(pred, succ, loadProp, storeProp)
SharedFlowStep::loadStoreStep(pred, succ, loadProp, storeProp)
or
cfg.isAdditionalLoadStoreStep(pred, succ, loadProp, storeProp)
or
loadProp = storeProp and
(
any(AdditionalFlowStep s).loadStoreStep(pred, succ, loadProp)
SharedFlowStep::loadStoreStep(pred, succ, loadProp)
or
cfg.isAdditionalLoadStoreStep(pred, succ, loadProp)
)
@@ -1356,19 +1461,20 @@ private predicate summarizedHigherOrderCall(
DataFlow::Node arg, DataFlow::Node cb, int i, DataFlow::Configuration cfg, PathSummary summary
) {
exists(
Function f, DataFlow::InvokeNode outer, DataFlow::InvokeNode inner, int j,
DataFlow::Node innerArg, DataFlow::SourceNode cbParm, PathSummary oldSummary
Function f, DataFlow::InvokeNode inner, int j, DataFlow::Node innerArg,
DataFlow::SourceNode cbParm, PathSummary oldSummary
|
// Captured flow does not need to be summarized - it is handled by the local case in `higherOrderCall`.
not arg = DataFlow::capturedVariableNode(_) and
summarizedHigherOrderCallAux(f, outer, arg, innerArg, cfg, oldSummary, cbParm, inner, j, cb)
not arg = DataFlow::capturedVariableNode(_)
|
// direct higher-order call
cbParm.flowsTo(inner.getCalleeNode()) and
summarizedHigherOrderCallAux(f, arg, innerArg, cfg, oldSummary, cbParm, inner, j, cb) and
inner = cbParm.getAnInvocation() and
i = j and
summary = oldSummary
or
// indirect higher-order call
summarizedHigherOrderCallAux(f, arg, innerArg, cfg, oldSummary, cbParm, inner, j, cb) and
exists(DataFlow::Node cbArg, PathSummary newSummary |
cbParm.flowsTo(cbArg) and
summarizedHigherOrderCall(innerArg, cbArg, i, cfg, newSummary) and
@@ -1382,14 +1488,17 @@ private predicate summarizedHigherOrderCall(
*/
pragma[noinline]
private predicate summarizedHigherOrderCallAux(
Function f, DataFlow::InvokeNode outer, DataFlow::Node arg, DataFlow::Node innerArg,
DataFlow::Configuration cfg, PathSummary oldSummary, DataFlow::SourceNode cbParm,
DataFlow::InvokeNode inner, int j, DataFlow::Node cb
Function f, DataFlow::Node arg, DataFlow::Node innerArg, DataFlow::Configuration cfg,
PathSummary oldSummary, DataFlow::SourceNode cbParm, DataFlow::InvokeNode inner, int j,
DataFlow::Node cb
) {
reachableFromInput(f, outer, arg, innerArg, cfg, oldSummary) and
// Only track actual parameter flow.
argumentPassing(outer, cb, f, cbParm) and
innerArg = inner.getArgument(j)
exists(DataFlow::Node outer1, DataFlow::Node outer2 |
reachableFromInput(f, outer1, arg, innerArg, cfg, oldSummary) and
outer1 = pragma[only_bind_into](outer2) and
// Only track actual parameter flow.
argumentPassing(outer2, cb, f, cbParm) and
innerArg = inner.getArgument(j)
)
}
/**

View File

@@ -531,6 +531,13 @@ module DataFlow {
predicate isPrivateField() {
getPropertyName().charAt(0) = "#" and getPropertyNameExpr() instanceof Label
}
/**
* Gets an accessor (`get` or `set` method) that may be invoked by this property reference.
*/
final DataFlow::FunctionNode getAnAccessorCallee() {
result = CallGraph::getAnAccessorCallee(this)
}
}
/**

View File

@@ -59,18 +59,16 @@ class ParameterNode extends DataFlow::SourceNode {
* ```
*/
class InvokeNode extends DataFlow::SourceNode {
DataFlow::Impl::InvokeNodeDef impl;
InvokeNode() { this = impl }
InvokeNode() { this instanceof DataFlow::Impl::InvokeNodeDef }
/** Gets the syntactic invoke expression underlying this function invocation. */
InvokeExpr getInvokeExpr() { result = impl.getInvokeExpr() }
InvokeExpr getInvokeExpr() { result = this.(DataFlow::Impl::InvokeNodeDef).getInvokeExpr() }
/** Gets the name of the function or method being invoked, if it can be determined. */
string getCalleeName() { result = impl.getCalleeName() }
string getCalleeName() { result = this.(DataFlow::Impl::InvokeNodeDef).getCalleeName() }
/** Gets the data flow node specifying the function to be called. */
DataFlow::Node getCalleeNode() { result = impl.getCalleeNode() }
DataFlow::Node getCalleeNode() { result = this.(DataFlow::Impl::InvokeNodeDef).getCalleeNode() }
/**
* Gets the data flow node corresponding to the `i`th argument of this invocation.
@@ -91,10 +89,10 @@ class InvokeNode extends DataFlow::SourceNode {
* but the position of `z` cannot be determined, hence there are no first and second
* argument nodes.
*/
DataFlow::Node getArgument(int i) { result = impl.getArgument(i) }
DataFlow::Node getArgument(int i) { result = this.(DataFlow::Impl::InvokeNodeDef).getArgument(i) }
/** Gets the data flow node corresponding to an argument of this invocation. */
DataFlow::Node getAnArgument() { result = impl.getAnArgument() }
DataFlow::Node getAnArgument() { result = this.(DataFlow::Impl::InvokeNodeDef).getAnArgument() }
/** Gets the data flow node corresponding to the last argument of this invocation. */
DataFlow::Node getLastArgument() { result = getArgument(getNumArgument() - 1) }
@@ -111,10 +109,12 @@ class InvokeNode extends DataFlow::SourceNode {
* ```
* .
*/
DataFlow::Node getASpreadArgument() { result = impl.getASpreadArgument() }
DataFlow::Node getASpreadArgument() {
result = this.(DataFlow::Impl::InvokeNodeDef).getASpreadArgument()
}
/** Gets the number of arguments of this invocation, if it can be determined. */
int getNumArgument() { result = impl.getNumArgument() }
int getNumArgument() { result = this.(DataFlow::Impl::InvokeNodeDef).getNumArgument() }
Function getEnclosingFunction() { result = getBasicBlock().getContainer() }
@@ -256,14 +256,14 @@ class InvokeNode extends DataFlow::SourceNode {
* ```
*/
class CallNode extends InvokeNode {
override DataFlow::Impl::CallNodeDef impl;
CallNode() { this instanceof DataFlow::Impl::CallNodeDef }
/**
* Gets the data flow node corresponding to the receiver expression of this method call.
*
* For example, the receiver of `x.m()` is `x`.
*/
DataFlow::Node getReceiver() { result = impl.getReceiver() }
DataFlow::Node getReceiver() { result = this.(DataFlow::Impl::CallNodeDef).getReceiver() }
}
/**
@@ -277,10 +277,10 @@ class CallNode extends InvokeNode {
* ```
*/
class MethodCallNode extends CallNode {
override DataFlow::Impl::MethodCallNodeDef impl;
MethodCallNode() { this instanceof DataFlow::Impl::MethodCallNodeDef }
/** Gets the name of the invoked method, if it can be determined. */
string getMethodName() { result = impl.getMethodName() }
string getMethodName() { result = this.(DataFlow::Impl::MethodCallNodeDef).getMethodName() }
/**
* Holds if this data flow node calls method `methodName` on receiver node `receiver`.
@@ -300,7 +300,7 @@ class MethodCallNode extends CallNode {
* ```
*/
class NewNode extends InvokeNode {
override DataFlow::Impl::NewNodeDef impl;
NewNode() { this instanceof DataFlow::Impl::NewNodeDef }
}
/**
@@ -546,6 +546,16 @@ class ObjectLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode {
DataFlow::Node getASpreadProperty() {
result = astNode.getAProperty().(SpreadProperty).getInit().(SpreadElement).getOperand().flow()
}
/** Gets the property getter of the given name, installed on this object literal. */
DataFlow::FunctionNode getPropertyGetter(string name) {
result = astNode.getPropertyByName(name).(PropertyGetter).getInit().flow()
}
/** Gets the property setter of the given name, installed on this object literal. */
DataFlow::FunctionNode getPropertySetter(string name) {
result = astNode.getPropertyByName(name).(PropertySetter).getInit().flow()
}
}
/**

View File

@@ -828,13 +828,13 @@ module TaintTracking {
/**
* A taint propagating data flow edge arising from URL parameter parsing.
*/
private class UrlSearchParamsTaintStep extends DataFlow::AdditionalFlowStep, DataFlow::ValueNode {
private class UrlSearchParamsTaintStep extends DataFlow::SharedFlowStep {
/**
* Holds if `succ` is a `URLSearchParams` providing access to the
* parameters encoded in `pred`.
*/
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
isUrlSearchParams(succ, pred) and succ = this
isUrlSearchParams(succ, pred)
}
/**
@@ -847,17 +847,14 @@ module TaintTracking {
* which can be accessed using a `get` or `getAll` call. (See getableUrlPseudoProperty())
*/
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
succ = this and
(
prop = ["searchParams", "hash", "search", hiddenUrlPseudoProperty()] and
exists(DataFlow::NewNode newUrl | succ = newUrl |
newUrl = DataFlow::globalVarRef("URL").getAnInstantiation() and
pred = newUrl.getArgument(0)
)
or
prop = getableUrlPseudoProperty() and
isUrlSearchParams(succ, pred)
prop = ["searchParams", "hash", "search", hiddenUrlPseudoProperty()] and
exists(DataFlow::NewNode newUrl | succ = newUrl |
newUrl = DataFlow::globalVarRef("URL").getAnInstantiation() and
pred = newUrl.getArgument(0)
)
or
prop = getableUrlPseudoProperty() and
isUrlSearchParams(succ, pred)
}
/**
@@ -869,7 +866,6 @@ module TaintTracking {
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
succ = this and
loadProp = hiddenUrlPseudoProperty() and
storeProp = getableUrlPseudoProperty() and
exists(DataFlow::PropRead read | read = succ |
@@ -884,7 +880,6 @@ module TaintTracking {
* This step is used to load the value stored in the pseudo-property `getableUrlPseudoProperty()`.
*/
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
succ = this and
prop = getableUrlPseudoProperty() and
// this is a call to `get` or `getAll` on a `URLSearchParams` object
exists(string m, DataFlow::MethodCallNode call | call = succ |

View File

@@ -102,7 +102,7 @@ private module NodeTracking {
predicate localFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
pred = succ.getAPredecessor()
or
any(DataFlow::AdditionalFlowStep afs).step(pred, succ)
DataFlow::SharedFlowStep::step(pred, succ)
or
localExceptionStep(pred, succ)
}

View File

@@ -9,6 +9,7 @@
private import javascript
private import internal.FlowSteps
private import internal.StepSummary
private import internal.Unit
private import semmle.javascript.internal.CachedStages
private newtype TTypeTracker = MkTypeTracker(Boolean hasCall, OptionalPropertyName prop)
@@ -252,6 +253,12 @@ class TypeBackTracker extends TTypeBackTracker {
*/
predicate start() { hasReturn = false and prop = "" }
/**
* Holds if this is the starting point of type backtracking, and the value is in the property named `propName`.
* The type tracking only ends after the property has been stored.
*/
predicate isInProp(PropertyName propName) { hasReturn = false and prop = propName }
/**
* Holds if this is the end point of type tracking.
*/
@@ -328,6 +335,89 @@ module TypeBackTracker {
}
/**
* A data flow edge that should be followed by type tracking.
*
* Unlike `SharedFlowStep`, this type of edge does not affect
* the local data flow graph, and is not used by data-flow configurations.
*
* Note: For performance reasons, all subclasses of this class should be part
* of the standard library. For query-specific steps, consider including the
* custom steps in the type-tracking predicate itself.
*/
class SharedTypeTrackingStep extends Unit {
/**
* Holds if type-tracking should step from `pred` to `succ`.
*/
predicate step(DataFlow::Node pred, DataFlow::Node succ) { none() }
/**
* Holds if type-tracking should step from `pred` into the `prop` property of `succ`.
*/
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
* Holds if type-tracking should step from the `prop` property of `pred` to `succ`.
*/
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { none() }
/**
* Holds if type-tracking should step from the `prop` property of `pred` to the same property in `succ`.
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
* Holds if type-tracking should step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
*/
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
) {
none()
}
}
/** Provides access to the steps contributed by subclasses of `SharedTypeTrackingStep`. */
module SharedTypeTrackingStep {
/**
* Holds if type-tracking should step from `pred` to `succ`.
*/
predicate step(DataFlow::Node pred, DataFlow::Node succ) {
any(SharedTypeTrackingStep s).step(pred, succ)
}
/**
* Holds if type-tracking should step from `pred` into the `prop` property of `succ`.
*/
predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(SharedTypeTrackingStep s).storeStep(pred, succ, prop)
}
/**
* Holds if type-tracking should step from the `prop` property of `pred` to `succ`.
*/
predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(SharedTypeTrackingStep s).loadStep(pred, succ, prop)
}
/**
* Holds if type-tracking should step from the `prop` property of `pred` to the same property in `succ`.
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(SharedTypeTrackingStep s).loadStoreStep(pred, succ, prop)
}
/**
* Holds if type-tracking should step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
*/
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
) {
any(SharedTypeTrackingStep s).loadStoreStep(pred, succ, loadProp, storeProp)
}
}
/**
* DEPRECATED. Use `SharedTypeTrackingStep` instead.
*
* A data flow edge that should be followed by type tracking.
*
* Unlike `AdditionalFlowStep`, this type of edge does not affect
@@ -337,7 +427,10 @@ module TypeBackTracker {
* of the standard library. For query-specific steps, consider including the
* custom steps in the type-tracking predicate itself.
*/
abstract class AdditionalTypeTrackingStep extends DataFlow::Node {
deprecated class AdditionalTypeTrackingStep = LegacyTypeTrackingStep;
// Internal version of AdditionalTypeTrackingStep that we can reference without deprecation warnings.
abstract private class LegacyTypeTrackingStep extends DataFlow::Node {
/**
* Holds if type-tracking should step from `pred` to `succ`.
*/
@@ -358,3 +451,21 @@ abstract class AdditionalTypeTrackingStep extends DataFlow::Node {
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
}
private class LegacyStepAsSharedTypeTrackingStep extends SharedTypeTrackingStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
any(LegacyTypeTrackingStep s).step(pred, succ)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(LegacyTypeTrackingStep s).storeStep(pred, succ, prop)
}
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
any(LegacyTypeTrackingStep s).loadStep(pred, succ, prop)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(LegacyTypeTrackingStep s).loadStoreStep(pred, succ, prop)
}
}

View File

@@ -162,4 +162,46 @@ module CallGraph {
)
)
}
/** Holds if a property setter named `name` exists in a class. */
private predicate isSetterName(string name) {
exists(any(DataFlow::ClassNode cls).getInstanceMember(name, DataFlow::MemberKind::setter()))
}
/**
* Gets a property write that assigns to the property `name` on an instance of this class,
* and `name` is the name of a property setter.
*/
private DataFlow::PropWrite getAnInstanceMemberAssignment(DataFlow::ClassNode cls, string name) {
isSetterName(name) and // restrict size of predicate
result = cls.getAnInstanceReference().getAPropertyWrite(name)
or
exists(DataFlow::ClassNode subclass |
result = getAnInstanceMemberAssignment(subclass, name) and
not exists(subclass.getInstanceMember(name, DataFlow::MemberKind::setter())) and
cls = subclass.getADirectSuperClass()
)
}
/**
* Gets a getter or setter invoked as a result of the given property access.
*/
cached
DataFlow::FunctionNode getAnAccessorCallee(DataFlow::PropRef ref) {
exists(DataFlow::ClassNode cls, string name |
ref = cls.getAnInstanceMemberAccess(name) and
result = cls.getInstanceMember(name, DataFlow::MemberKind::getter())
or
ref = getAnInstanceMemberAssignment(cls, name) and
result = cls.getInstanceMember(name, DataFlow::MemberKind::setter())
)
or
exists(DataFlow::ObjectLiteralNode object, string name |
ref = object.getAPropertyRead(name) and
result = object.getPropertyGetter(name)
or
ref = object.getAPropertyWrite(name) and
result = object.getPropertySetter(name)
)
}
}

View File

@@ -25,7 +25,9 @@ predicate shouldTrackProperties(AbstractValue obj) {
*/
pragma[noinline]
predicate returnExpr(Function f, DataFlow::Node source, DataFlow::Node sink) {
sink.asExpr() = f.getAReturnedExpr() and source = sink
sink.asExpr() = f.getAReturnedExpr() and
source = sink and
not f = any(SetterMethodDeclaration decl).getBody()
}
/**
@@ -39,9 +41,9 @@ predicate localFlowStep(
) {
pred = succ.getAPredecessor() and predlbl = succlbl
or
any(DataFlow::AdditionalFlowStep afs).step(pred, succ) and predlbl = succlbl
DataFlow::SharedFlowStep::step(pred, succ) and predlbl = succlbl
or
any(DataFlow::AdditionalFlowStep afs).step(pred, succ, predlbl, succlbl)
DataFlow::SharedFlowStep::step(pred, succ, predlbl, succlbl)
or
exists(boolean vp | configuration.isAdditionalFlowStep(pred, succ, vp) |
vp = true and
@@ -120,7 +122,11 @@ private module CachedSteps {
* Holds if `invk` may invoke `f`.
*/
cached
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
predicate calls(DataFlow::SourceNode invk, Function f) {
f = invk.(DataFlow::InvokeNode).getACallee(0)
or
f = invk.(DataFlow::PropRef).getAnAccessorCallee().getFunction()
}
private predicate callsBoundInternal(
DataFlow::InvokeNode invk, Function f, int boundArgs, boolean contextDependent
@@ -177,11 +183,11 @@ private module CachedSteps {
*/
cached
predicate argumentPassing(
DataFlow::InvokeNode invk, DataFlow::ValueNode arg, Function f, DataFlow::SourceNode parm
DataFlow::SourceNode invk, DataFlow::Node arg, Function f, DataFlow::SourceNode parm
) {
calls(invk, f) and
(
exists(int i | arg = invk.getArgument(i) |
exists(int i | arg = invk.(DataFlow::InvokeNode).getArgument(i) |
exists(Parameter p |
f.getParameter(i) = p and
not p.isRestParameter() and
@@ -193,12 +199,19 @@ private module CachedSteps {
or
arg = invk.(DataFlow::CallNode).getReceiver() and
parm = DataFlow::thisNode(f)
or
arg = invk.(DataFlow::PropRef).getBase() and
parm = DataFlow::thisNode(f)
or
arg = invk.(DataFlow::PropWrite).getRhs() and
parm = DataFlow::parameterNode(f.getParameter(0))
)
or
exists(DataFlow::Node callback, int i, Parameter p |
exists(DataFlow::Node callback, int i, Parameter p, Function target |
invk.(DataFlow::PartialInvokeNode).isPartialArgument(callback, arg, i) and
partiallyCalls(invk, callback, f) and
f.getParameter(i) = p and
f = pragma[only_bind_into](target) and
target.getParameter(i) = p and
not p.isRestParameter() and
parm = DataFlow::parameterNode(p)
)
@@ -213,7 +226,7 @@ private module CachedSteps {
callsBound(invk, f, boundArgs) and
f.getParameter(boundArgs + i) = p and
not p.isRestParameter() and
arg = invk.getArgument(i) and
arg = invk.(DataFlow::InvokeNode).getArgument(i) and
parm = DataFlow::parameterNode(p)
)
}

View File

@@ -16,7 +16,7 @@ private class Unit extends TUnit {
* Internal extension point for adding flow edges prior to call graph construction
* and type tracking.
*
* Steps added here will be added to both `AdditionalFlowStep` and `AdditionalTypeTrackingStep`.
* Steps added here will be added to both `SharedFlowStep` and `SharedTypeTrackingStep`.
*
* Contributing steps that rely on type tracking will lead to negative recursion.
*/
@@ -40,6 +40,15 @@ class PreCallGraphStep extends Unit {
* Holds if there is a step from the `prop` property of `pred` to the same property in `succ`.
*/
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) { none() }
/**
* Holds if there is a step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
*/
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
) {
none()
}
}
module PreCallGraphStep {
@@ -75,62 +84,61 @@ module PreCallGraphStep {
predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
any(PreCallGraphStep s).loadStoreStep(pred, succ, prop)
}
}
private class NodeWithPreCallGraphStep extends DataFlow::Node {
NodeWithPreCallGraphStep() {
PreCallGraphStep::step(this, _)
or
PreCallGraphStep::storeStep(this, _, _)
or
PreCallGraphStep::loadStep(this, _, _)
or
PreCallGraphStep::loadStoreStep(this, _, _)
/**
* Holds if there is a step from the `loadProp` property of `pred` to the `storeProp` property in `succ`.
*/
predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
) {
any(PreCallGraphStep s).loadStoreStep(pred, succ, loadProp, storeProp)
}
}
private class AdditionalFlowStepFromPreCallGraph extends NodeWithPreCallGraphStep,
DataFlow::AdditionalFlowStep {
private class SharedFlowStepFromPreCallGraph extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = this and
PreCallGraphStep::step(this, succ)
PreCallGraphStep::step(pred, succ)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
pred = this and
PreCallGraphStep::storeStep(this, succ, prop)
PreCallGraphStep::storeStep(pred, succ, prop)
}
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
pred = this and
PreCallGraphStep::loadStep(this, succ, prop)
PreCallGraphStep::loadStep(pred, succ, prop)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
pred = this and
PreCallGraphStep::loadStoreStep(this, succ, prop)
PreCallGraphStep::loadStoreStep(pred, succ, prop)
}
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::Node succ, string loadProp, string storeProp
) {
PreCallGraphStep::loadStoreStep(pred, succ, loadProp, storeProp)
}
}
private class AdditionalTypeTrackingStepFromPreCallGraph extends NodeWithPreCallGraphStep,
DataFlow::AdditionalTypeTrackingStep {
private class SharedTypeTrackingStepFromPreCallGraph extends DataFlow::SharedTypeTrackingStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = this and
PreCallGraphStep::step(this, succ)
PreCallGraphStep::step(pred, succ)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
pred = this and
PreCallGraphStep::storeStep(this, succ, prop)
PreCallGraphStep::storeStep(pred, succ, prop)
}
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
pred = this and
PreCallGraphStep::loadStep(this, succ, prop)
PreCallGraphStep::loadStep(pred, succ, prop)
}
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
pred = this and
PreCallGraphStep::loadStoreStep(this, succ, prop)
PreCallGraphStep::loadStoreStep(pred, succ, prop)
}
override predicate loadStoreStep(
DataFlow::Node pred, DataFlow::SourceNode succ, string loadProp, string storeProp
) {
PreCallGraphStep::loadStoreStep(pred, succ, loadProp, storeProp)
}
}

View File

@@ -1,50 +1,66 @@
import javascript
private import semmle.javascript.dataflow.TypeTracking
private import semmle.javascript.internal.CachedStages
private import FlowSteps
class PropertyName extends string {
PropertyName() {
this = any(DataFlow::PropRef pr).getPropertyName()
or
AccessPath::isAssignedInUniqueFile(this)
or
exists(AccessPath::getAnAssignmentTo(_, this))
or
this instanceof TypeTrackingPseudoProperty
cached
private module Cached {
cached
module Public {
cached
predicate forceStage() { Stages::TypeTracking::ref() }
cached
class PropertyName extends string {
cached
PropertyName() {
this = any(DataFlow::PropRef pr).getPropertyName()
or
AccessPath::isAssignedInUniqueFile(this)
or
exists(AccessPath::getAnAssignmentTo(_, this))
or
SharedTypeTrackingStep::loadStep(_, _, this)
or
SharedTypeTrackingStep::storeStep(_, _, this)
or
SharedTypeTrackingStep::loadStoreStep(_, _, this, _)
or
SharedTypeTrackingStep::loadStoreStep(_, _, _, this)
}
}
/**
* A description of a step on an inter-procedural data flow path.
*/
cached
newtype TStepSummary =
LevelStep() or
CallStep() or
ReturnStep() or
StoreStep(PropertyName prop) or
LoadStep(PropertyName prop) or
CopyStep(PropertyName prop) or
LoadStoreStep(PropertyName fromProp, PropertyName toProp) {
SharedTypeTrackingStep::loadStoreStep(_, _, fromProp, toProp)
}
}
/**
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
*/
cached
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
exists(DataFlow::Node mid | pred.flowsTo(mid) | StepSummary::smallstep(mid, succ, summary))
}
}
import Cached::Public
class OptionalPropertyName extends string {
OptionalPropertyName() { this instanceof PropertyName or this = "" }
}
/**
* A pseudo-property that can be used in type-tracking.
*/
abstract class TypeTrackingPseudoProperty extends string {
bindingset[this]
TypeTrackingPseudoProperty() { any() }
/**
* Gets a property name that `this` can be copied to in a `LoadStoreStep(this, result)`.
*/
string getLoadStoreToProp() { none() }
}
/**
* A description of a step on an inter-procedural data flow path.
*/
newtype TStepSummary =
LevelStep() or
CallStep() or
ReturnStep() or
StoreStep(PropertyName prop) or
LoadStep(PropertyName prop) or
CopyStep(PropertyName prop) or
LoadStoreStep(PropertyName fromProp, PropertyName toProp) {
exists(TypeTrackingPseudoProperty prop | fromProp = prop and toProp = prop.getLoadStoreToProp())
}
/**
* INTERNAL: Use `TypeTracker` or `TypeBackTracker` instead.
*
@@ -75,10 +91,7 @@ module StepSummary {
/**
* INTERNAL: Use `SourceNode.track()` or `SourceNode.backtrack()` instead.
*/
cached
predicate step(DataFlow::SourceNode pred, DataFlow::SourceNode succ, StepSummary summary) {
exists(DataFlow::Node mid | pred.flowsTo(mid) | smallstep(mid, succ, summary))
}
predicate step = Cached::step/3;
/**
* INTERNAL: Use `TypeBackTracker.smallstep()` instead.
@@ -111,17 +124,22 @@ module StepSummary {
basicLoadStep(pred, succ, prop) and
summary = LoadStep(prop)
or
any(AdditionalTypeTrackingStep st).storeStep(pred, succ, prop) and
SharedTypeTrackingStep::storeStep(pred, succ, prop) and
summary = StoreStep(prop)
or
any(AdditionalTypeTrackingStep st).loadStep(pred, succ, prop) and
SharedTypeTrackingStep::loadStep(pred, succ, prop) and
summary = LoadStep(prop)
or
any(AdditionalTypeTrackingStep st).loadStoreStep(pred, succ, prop) and
SharedTypeTrackingStep::loadStoreStep(pred, succ, prop) and
summary = CopyStep(prop)
)
or
any(AdditionalTypeTrackingStep st).step(pred, succ) and
exists(string fromProp, string toProp |
SharedTypeTrackingStep::loadStoreStep(pred, succ, fromProp, toProp) and
summary = LoadStoreStep(fromProp, toProp)
)
or
SharedTypeTrackingStep::step(pred, succ) and
summary = LevelStep()
or
// Store to global access path

View File

@@ -194,26 +194,22 @@ module EventDispatch {
}
/**
* A taint-step that models data-flow between event handlers and event dispatchers.
* A flow-step that models data-flow between event handlers and event dispatchers.
*/
private class EventEmitterTaintStep extends DataFlow::AdditionalFlowStep {
EventRegistration reg;
EventDispatch dispatch;
EventEmitterTaintStep() {
this = dispatch and
reg = dispatch.getAReceiver() and
not dispatch.getChannel() != reg.getChannel()
}
private class EventEmitterFlowStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(int i | i >= 0 |
pred = dispatch.getSentItem(i) and
succ = reg.getReceivedItem(i)
exists(EventRegistration reg, EventDispatch dispatch |
reg = dispatch.getAReceiver() and
not dispatch.getChannel() != reg.getChannel()
|
exists(int i | i >= 0 |
pred = dispatch.getSentItem(i) and
succ = reg.getReceivedItem(i)
)
or
dispatch = reg.getAReturnDispatch() and
pred = reg.getAReturnedValue() and
succ = dispatch
)
or
dispatch = reg.getAReturnDispatch() and
pred = reg.getAReturnedValue() and
succ = dispatch
}
}

View File

@@ -6,6 +6,7 @@ import javascript
private import semmle.javascript.DynamicPropertyAccess
private import semmle.javascript.dataflow.internal.StepSummary
private import semmle.javascript.dataflow.internal.CallGraphs
private import DataFlow::PseudoProperties as PseudoProperties
module HTTP {
/**
@@ -689,33 +690,30 @@ module HTTP {
isDecoratedCall(result, candidate)
}
private string mapValueProp() {
result = [PseudoProperties::mapValueAll(), PseudoProperties::mapValueUnknownKey()]
}
/**
* A collection that contains one or more route potential handlers.
*/
private class ContainerCollection extends HTTP::RouteHandlerCandidateContainer::Range {
private class ContainerCollection extends HTTP::RouteHandlerCandidateContainer::Range,
DataFlow::NewNode {
ContainerCollection() {
this = DataFlow::globalVarRef("Map").getAnInstantiation() and // restrict to Map for now
exists(
CollectionFlowStep store, DataFlow::Node storeTo, DataFlow::Node input,
RouteHandlerCandidate candidate
|
this.flowsTo(storeTo) and
store.store(input, storeTo, _) and
candidate.flowsTo(input)
exists(DataFlow::Node use |
DataFlow::SharedTypeTrackingStep::storeStep(use, this, mapValueProp()) and
use.getALocalSource() instanceof RouteHandlerCandidate
)
}
override DataFlow::SourceNode getRouteHandler(DataFlow::SourceNode access) {
result instanceof RouteHandlerCandidate and
exists(
DataFlow::Node input, TypeTrackingPseudoProperty key, CollectionFlowStep store,
CollectionFlowStep load, DataFlow::Node storeTo, DataFlow::Node loadFrom
|
this.flowsTo(storeTo) and
store.store(input, storeTo, key) and
exists(DataFlow::Node input, string key, DataFlow::Node loadFrom |
getAPossiblyDecoratedHandler(result).flowsTo(input) and
DataFlow::SharedTypeTrackingStep::storeStep(input, this, key) and
ref(this).flowsTo(loadFrom) and
load.load(loadFrom, access, key)
DataFlow::SharedTypeTrackingStep::loadStep(loadFrom, access,
[key, PseudoProperties::mapValueAll()])
)
}
}

View File

@@ -164,22 +164,15 @@ private module Immutable {
/**
* A dataflow step for an immutable collection.
*/
class ImmutableConstructionStep extends DataFlow::AdditionalFlowStep {
ImmutableConstructionStep() { this = [loadStep(_, _), storeStep(_, _), step(_)] }
class ImmutableConstructionStep extends DataFlow::SharedFlowStep {
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
this = loadStep(pred, prop) and
succ = this
succ = loadStep(pred, prop)
}
override predicate storeStep(DataFlow::Node pred, DataFlow::SourceNode succ, string prop) {
this = storeStep(pred, prop) and
succ = this
succ = storeStep(pred, prop)
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
this = step(pred) and
succ = this
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) { succ = step(pred) }
}
}

View File

@@ -36,18 +36,11 @@ module Koa {
/**
* A Koa route handler.
*/
class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode {
Function function;
RouteHandler() {
function = astNode and
any(RouteSetup setup).getARouteHandler() = this
}
abstract class RouteHandler extends HTTP::Servers::StandardRouteHandler, DataFlow::SourceNode {
/**
* Gets the parameter of the route handler that contains the context object.
*/
Parameter getContextParameter() { result = function.getParameter(0) }
Parameter getContextParameter() { result = getAFunctionValue().getFunction().getParameter(0) }
/**
* Gets an expression that contains the "context" object of
@@ -70,6 +63,35 @@ module Koa {
* object of a route handler invocation.
*/
Expr getARequestOrContextExpr() { result = getARequestExpr() or result = getAContextExpr() }
/**
* Gets a reference to a request parameter defined by this route handler.
*/
DataFlow::Node getARequestParameterAccess() {
none() // overriden in subclasses.
}
/**
* Gets a dataflow node that can be given to a `RouteSetup` to register the handler.
*/
abstract DataFlow::SourceNode getARouteHandlerRegistrationObject();
}
/**
* A koa route handler registered directly with a route-setup.
* Like:
* ```JavaScript
* var route = require('koa-route');
* var app = new Koa();
* app.use((context, next) => {
* ...
* });
* ```
*/
private class StandardRouteHandler extends RouteHandler {
StandardRouteHandler() { any(RouteSetup setup).getARouteHandler() = this }
override DataFlow::SourceNode getARouteHandlerRegistrationObject() { result = this }
}
/**
@@ -100,6 +122,77 @@ module Koa {
}
}
/**
* A Koa route handler registered using a routing library.
*
* Example of what that could look like:
* ```JavaScript
* const router = require('koa-router')();
* const Koa = require('koa');
* const app = new Koa();
* router.get('/', async (ctx, next) => {
* // route handler stuff
* });
* app.use(router.routes());
* ```
*/
private class RoutedRouteHandler extends RouteHandler {
DataFlow::InvokeNode router;
DataFlow::MethodCallNode call;
RoutedRouteHandler() {
router = DataFlow::moduleImport(["@koa/router", "koa-router"]).getAnInvocation() and
call =
router
.getAChainedMethodCall([
"use", "get", "post", "put", "link", "unlink", "delete", "del", "head", "options",
"patch", "all"
]) and
this.flowsTo(call.getArgument(any(int i | i >= 1)))
}
override DataFlow::SourceNode getARouteHandlerRegistrationObject() {
result = call
or
result = router.getAMethodCall("routes")
}
}
/**
* A route handler registered using the `koa-route` library.
*
* Example of how `koa-route` can be used:
* ```JavaScript
* var route = require('koa-route');
* var Koa = require('koa');
* var app = new Koa();
*
* app.use(route.get('/pets', (context, param1, param2, param3, ...params) => {
* // route handler stuff
* }));
*/
class KoaRouteHandler extends RouteHandler {
DataFlow::CallNode call;
KoaRouteHandler() {
call =
DataFlow::moduleMember("koa-route",
[
"all", "acl", "bind", "checkout", "connect", "copy", "delete", "del", "get", "head",
"link", "lock", "msearch", "merge", "mkactivity", "mkcalendar", "mkcol", "move",
"notify", "options", "patch", "post", "propfind", "proppatch", "purge", "put", "rebind",
"report", "search", "subscribe", "trace", "unbind", "unlink", "unlock", "unsubscribe"
]).getACall() and
this.flowsTo(call.getArgument(1))
}
override DataFlow::Node getARequestParameterAccess() {
result = call.getABoundCallbackParameter(1, any(int i | i >= 1))
}
override DataFlow::SourceNode getARouteHandlerRegistrationObject() { result = call }
}
/**
* A Koa request source, that is, an access to the `request` property
* of a context object.
@@ -189,6 +282,9 @@ module Koa {
kind = "parameter" and
this = getAQueryParameterAccess(rh)
or
kind = "parameter" and
this = rh.getARequestParameterAccess()
or
exists(Expr e | rh.getARequestOrContextExpr() = e |
// `ctx.request.url`, `ctx.request.originalUrl`, or `ctx.request.href`
exists(string propName |
@@ -202,6 +298,10 @@ module Koa {
propName = "href"
)
or
// params, when handler is registered by `koa-router` or similar.
kind = "parameter" and
this.asExpr().(PropAccess).accesses(e, "params")
or
// `ctx.request.body`
e instanceof RequestExpr and
kind = "body" and
@@ -285,7 +385,13 @@ module Koa {
getMethodName() = "use"
}
override DataFlow::SourceNode getARouteHandler() { result.flowsToExpr(getArgument(0)) }
override DataFlow::SourceNode getARouteHandler() {
// `StandardRouteHandler` uses this predicate in it's charpred, so making this predicate return a `RouteHandler` would give an empty recursion.
result.flowsToExpr(getArgument(0))
or
// For the route-handlers that does not depend on this predicate in their charpred.
result.(RouteHandler).getARouteHandlerRegistrationObject().flowsToExpr(getArgument(0))
}
override Expr getServer() { result = server }
}

View File

@@ -360,9 +360,9 @@ module LodashUnderscore {
/**
* A data flow step propagating an exception thrown from a callback to a Lodash/Underscore function.
*/
private class ExceptionStep extends DataFlow::CallNode, DataFlow::AdditionalFlowStep {
ExceptionStep() {
exists(string name | this = member(name).getACall() |
private class ExceptionStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::CallNode call, string name |
// Members ending with By, With, or While indicate that they are a variant of
// another function that takes a callback.
name.matches("%By") or
@@ -386,13 +386,12 @@ module LodashUnderscore {
name = "replace" or
name = "some" or
name = "transform"
|
call = member(name).getACall() and
pred = call.getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
succ = call.getExceptionalReturn()
)
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getAnArgument().(DataFlow::FunctionNode).getExceptionalReturn() and
succ = this.getExceptionalReturn()
}
}
/**

View File

@@ -126,37 +126,28 @@ module NextJS {
/**
* A step modelling the flow from the server-computed props object to the default exported function that renders the page.
*/
class NextJSStaticPropsStep extends DataFlow::AdditionalFlowStep, DataFlow::FunctionNode {
Module pageModule;
NextJSStaticPropsStep() {
pageModule = getAPagesModule() and
this = pageModule.getAnExportedValue("default").getAFunctionValue()
}
class NextJSStaticPropsStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getAPropsSource(pageModule) and
succ = this.getParameter(0)
exists(Module pageModule, DataFlow::FunctionNode function |
pageModule = getAPagesModule() and
function = pageModule.getAnExportedValue("default").getAFunctionValue() and
pred = getAPropsSource(pageModule) and
succ = function.getParameter(0)
)
}
}
/**
* A step modelling the flow from the server-computed props object to the default exported React component that renders the page.
*/
class NextJSStaticReactComponentPropsStep extends DataFlow::AdditionalFlowStep,
DataFlow::ValueNode {
Module pageModule;
ReactComponent component;
NextJSStaticReactComponentPropsStep() {
pageModule = getAPagesModule() and
this.getAstNode() = component and
this = pageModule.getAnExportedValue("default").getALocalSource()
}
class NextJSStaticReactComponentPropsStep extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = getAPropsSource(pageModule) and
succ = component.getADirectPropsAccess()
exists(Module pageModule, ReactComponent component |
pageModule = getAPagesModule() and
pageModule.getAnExportedValue("default").getALocalSource() = DataFlow::valueNode(component) and
pred = getAPropsSource(pageModule) and
succ = component.getADirectPropsAccess()
)
}
}

View File

@@ -0,0 +1,95 @@
/**
* Provides classes and predicates for reasoning about [puppeteer](https://www.npmjs.com/package/puppeteer).
*/
import javascript
/**
* Classes and predicates modelling the [puppeteer](https://www.npmjs.com/package/puppeteer) library.
*/
module Puppeteer {
/**
* A reference to a module import of puppeteer.
*/
private API::Node puppeteer() { result = API::moduleImport(["puppeteer", "puppeteer-core"]) }
/**
* A reference to a `Browser` from puppeteer.
*/
private API::Node browser() {
result = API::Node::ofType("puppeteer", "Browser")
or
result = puppeteer().getMember(["launch", "connect"]).getReturn().getPromised()
or
result = [page(), context(), target()].getMember("browser").getReturn()
}
/**
* A reference to a `Page` from puppeteer.
*/
API::Node page() {
result = API::Node::ofType("puppeteer", "Page")
or
result = [browser(), context()].getMember("newPage").getReturn().getPromised()
or
result = [browser(), context()].getMember("pages").getReturn().getPromised().getUnknownMember()
or
result = target().getMember("page").getReturn().getPromised()
}
/**
* A reference to a `Target` from puppeteer.
*/
private API::Node target() {
result = API::Node::ofType("puppeteer", "Target")
or
result = [page(), browser()].getMember("target").getReturn()
or
result = context().getMember("targets").getReturn().getUnknownMember()
or
result = target().getMember("opener").getReturn()
}
/**
* A reference to a `BrowserContext` from puppeteer.
*/
private API::Node context() {
result = API::Node::ofType("puppeteer", "BrowserContext")
or
result = [page(), target()].getMember("browserContext").getReturn()
or
result = browser().getMember("browserContexts").getReturn().getUnknownMember()
or
result = browser().getMember("createIncognitoBrowserContext").getReturn().getPromised()
or
result = browser().getMember("defaultBrowserContext").getReturn()
}
/**
* A call requesting a `Page` to navigate to some url, seen as a `ClientRequest`.
*/
private class PuppeteerGotoCall extends ClientRequest::Range, API::InvokeNode {
PuppeteerGotoCall() { this = page().getMember("goto").getACall() }
override DataFlow::Node getUrl() { result = getArgument(0) }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() { none() }
}
/**
* A call requesting a `Page` to load a stylesheet or script, seen as a `ClientRequest`.
*/
private class PuppeteerLoadResourceCall extends ClientRequest::Range, API::InvokeNode {
PuppeteerLoadResourceCall() {
this = page().getMember(["addStyleTag", "addScriptTag"]).getACall()
}
override DataFlow::Node getUrl() { result = getParameter(0).getMember("url").getARhs() }
override DataFlow::Node getHost() { none() }
override DataFlow::Node getADataNode() { none() }
}
}

View File

@@ -268,9 +268,11 @@ module SocketIO {
/** Gets the `i`th parameter through which data is received from a client. */
override DataFlow::SourceNode getReceivedItem(int i) {
exists(DataFlow::FunctionNode cb | cb = getListener() and result = cb.getParameter(i) |
exists(DataFlow::FunctionNode cb |
cb = getListener() and
result = cb.getParameter(i) and
// exclude last parameter if it looks like a callback
result != cb.getLastParameter() or not exists(result.getAnInvocation())
not (result = cb.getLastParameter() and exists(result.getAnInvocation()))
)
}

View File

@@ -43,9 +43,15 @@ private predicate execApi(string mod, int cmdArg, int optionsArg, boolean shell)
)
or
shell = true and
mod = "exec" and
optionsArg = -2 and
cmdArg = 0
(
mod = "exec" and
optionsArg = -2 and
cmdArg = 0
or
mod = "async-execute" and
optionsArg = 1 and
cmdArg = 0
)
}
private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::InvokeNode {

View File

@@ -219,7 +219,7 @@ module Stages {
or
AccessPath::DominatingPaths::hasDominatingWrite(_)
or
any(DataFlow::AdditionalFlowStep s).step(_, _)
DataFlow::SharedFlowStep::step(_, _)
}
}

View File

@@ -156,6 +156,12 @@ private module PersistentWebStorage {
result = DataFlow::globalVarRef(kind)
}
pragma[noinline]
WriteAccess getAWriteByName(string name, string kind) {
result.getKey() = name and
result.getKind() = kind
}
/**
* A read access.
*/
@@ -165,8 +171,10 @@ private module PersistentWebStorage {
ReadAccess() { this = webStorage(kind).getAMethodCall("getItem") }
override PersistentWriteAccess getAWrite() {
getArgument(0).mayHaveStringValue(result.(WriteAccess).getKey()) and
result.(WriteAccess).getKind() = kind
exists(string name |
getArgument(0).mayHaveStringValue(name) and
result = getAWriteByName(name, kind)
)
}
}

View File

@@ -255,15 +255,12 @@ module ExternalAPIUsedWithUntrustedData {
not exists(DataFlow::Node arg |
arg = this.getAnArgument() and not arg instanceof DeepObjectSink
|
TaintTracking::sharedTaintStep(arg, _)
or
exists(DataFlow::AdditionalFlowStep s |
s.step(arg, _) or
s.step(arg, _, _, _) or
s.loadStep(arg, _, _) or
s.storeStep(arg, _, _) or
s.loadStoreStep(arg, _, _)
)
TaintTracking::sharedTaintStep(arg, _) or
DataFlow::SharedFlowStep::step(arg, _) or
DataFlow::SharedFlowStep::step(arg, _, _, _) or
DataFlow::SharedFlowStep::loadStep(arg, _, _) or
DataFlow::SharedFlowStep::storeStep(arg, _, _) or
DataFlow::SharedFlowStep::loadStoreStep(arg, _, _)
)
}

View File

@@ -100,10 +100,10 @@ module PrototypePollutingAssignment {
// users wouldn't bother to call Object.create in that case.
result = DataFlow::globalVarRef("Object").getAMemberCall("create")
or
// Allow use of AdditionalFlowSteps to track a bit further
// Allow use of SharedFlowSteps to track a bit further
exists(DataFlow::Node mid |
prototypeLessObject(t.continue()).flowsTo(mid) and
any(DataFlow::AdditionalFlowStep s).step(mid, result)
DataFlow::SharedFlowStep::step(mid, result)
)
or
exists(DataFlow::TypeTracker t2 | result = prototypeLessObject(t2).track(t2, t))

View File

@@ -636,6 +636,20 @@ module TaintedPath {
SendPathSink() { this = DataFlow::moduleImport("send").getACall().getArgument(1) }
}
/**
* A path argument given to a `Page` in puppeteer, specifying where a pdf/screenshot should be saved.
*/
private class PuppeteerPath extends TaintedPath::Sink {
PuppeteerPath() {
this =
Puppeteer::page()
.getMember(["pdf", "screenshot"])
.getParameter(0)
.getMember("path")
.getARhs()
}
}
/**
* Holds if there is a step `src -> dst` mapping `srclabel` to `dstlabel` relevant for path traversal vulnerabilities.
*/