mirror of
https://github.com/github/codeql.git
synced 2025-12-27 06:06:32 +01:00
214 lines
6.3 KiB
Plaintext
214 lines
6.3 KiB
Plaintext
/**
|
|
* Provides classes for working with dynamic property accesses.
|
|
*/
|
|
|
|
import javascript
|
|
private import semmle.javascript.dataflow.InferredTypes
|
|
private import semmle.javascript.dataflow.DataFlow::DataFlow
|
|
private import semmle.javascript.dataflow.internal.FlowSteps
|
|
|
|
/**
|
|
* Gets a node that refers to an element of `array`, likely obtained
|
|
* as a result of enumerating the elements of the array.
|
|
*/
|
|
SourceNode getAnEnumeratedArrayElement(SourceNode array) {
|
|
exists(MethodCallNode call, string name |
|
|
call = array.getAMethodCall(name) and
|
|
name = ["forEach", "map"] and
|
|
result = call.getCallback(0).getParameter(0)
|
|
)
|
|
or
|
|
exists(DataFlow::PropRead read |
|
|
read = array.getAPropertyRead() and
|
|
not exists(read.getPropertyName()) and
|
|
not read.getPropertyNameExpr().analyze().getAType() = TTString() and
|
|
result = read
|
|
)
|
|
}
|
|
|
|
/**
|
|
* A data flow node that refers to the name of a property obtained by enumerating
|
|
* the properties of some object.
|
|
*/
|
|
abstract class EnumeratedPropName extends DataFlow::Node {
|
|
/**
|
|
* Gets the data flow node holding the object whose properties are being enumerated.
|
|
*
|
|
* For example, gets `src` in `for (var key in src)`.
|
|
*/
|
|
abstract DataFlow::Node getSourceObject();
|
|
|
|
/**
|
|
* Gets a source node that refers to the object whose properties are being enumerated.
|
|
*/
|
|
DataFlow::SourceNode getASourceObjectRef() {
|
|
result = AccessPath::getAnAliasedSourceNode(this.getSourceObject())
|
|
}
|
|
|
|
/**
|
|
* Gets a property read that accesses the corresponding property value in the source object.
|
|
*
|
|
* For example, gets `src[key]` in `for (var key in src) { src[key]; }`.
|
|
*/
|
|
SourceNode getASourceProp() {
|
|
exists(Node base, Node key |
|
|
dynamicPropReadStep(base, key, result) and
|
|
this.getASourceObjectRef().flowsTo(base) and
|
|
key.getImmediatePredecessor*() = this
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Property enumeration through `for-in` for `Object.keys` or similar.
|
|
*/
|
|
private class ForInEnumeratedPropName extends EnumeratedPropName {
|
|
DataFlow::Node object;
|
|
|
|
ForInEnumeratedPropName() {
|
|
exists(ForInStmt stmt |
|
|
this = DataFlow::lvalueNode(stmt.getLValue()) and
|
|
object = stmt.getIterationDomain().flow()
|
|
)
|
|
or
|
|
exists(CallNode call |
|
|
call = globalVarRef("Object").getAMemberCall("keys")
|
|
or
|
|
call = globalVarRef("Object").getAMemberCall("getOwnPropertyNames")
|
|
or
|
|
call = globalVarRef("Reflect").getAMemberCall("ownKeys")
|
|
|
|
|
object = call.getArgument(0) and
|
|
this = getAnEnumeratedArrayElement(call)
|
|
)
|
|
}
|
|
|
|
override Node getSourceObject() { result = object }
|
|
}
|
|
|
|
/**
|
|
* Property enumeration through `Object.entries`.
|
|
*/
|
|
private class EntriesEnumeratedPropName extends EnumeratedPropName {
|
|
CallNode entries;
|
|
SourceNode entry;
|
|
|
|
EntriesEnumeratedPropName() {
|
|
entries = globalVarRef("Object").getAMemberCall("entries") and
|
|
entry = getAnEnumeratedArrayElement(entries) and
|
|
this = entry.getAPropertyRead("0")
|
|
}
|
|
|
|
override DataFlow::Node getSourceObject() { result = entries.getArgument(0) }
|
|
|
|
override SourceNode getASourceProp() {
|
|
result = super.getASourceProp()
|
|
or
|
|
result = entry.getAPropertyRead("1")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a function that enumerates object properties when invoked.
|
|
*
|
|
* Invocations takes the following form:
|
|
* ```js
|
|
* fn(obj, (value, key, o) => { ... })
|
|
* ```
|
|
*/
|
|
private SourceNode propertyEnumerator() {
|
|
result = moduleImport("for-own") or
|
|
result = moduleImport("for-in") or
|
|
result = moduleMember("ramda", "forEachObjIndexed") or
|
|
result = LodashUnderscore::member("forEach") or
|
|
result = LodashUnderscore::member("each")
|
|
}
|
|
|
|
/**
|
|
* Property enumeration through a library function taking a callback.
|
|
*/
|
|
private class LibraryCallbackEnumeratedPropName extends EnumeratedPropName {
|
|
CallNode call;
|
|
FunctionNode callback;
|
|
|
|
LibraryCallbackEnumeratedPropName() {
|
|
call = propertyEnumerator().getACall() and
|
|
callback = call.getCallback(1) and
|
|
this = callback.getParameter(1)
|
|
}
|
|
|
|
override Node getSourceObject() { result = call.getArgument(0) }
|
|
|
|
override SourceNode getASourceObjectRef() {
|
|
result = super.getASourceObjectRef()
|
|
or
|
|
result = callback.getParameter(2)
|
|
}
|
|
|
|
override SourceNode getASourceProp() {
|
|
result = super.getASourceProp()
|
|
or
|
|
result = callback.getParameter(0)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A dynamic property access that is not obviously an array access.
|
|
*/
|
|
class DynamicPropRead extends DataFlow::SourceNode, DataFlow::ValueNode {
|
|
// Use IndexExpr instead of PropRead as we're not interested in implicit accesses like
|
|
// rest-patterns and for-of loops.
|
|
override IndexExpr astNode;
|
|
|
|
DynamicPropRead() {
|
|
not exists(astNode.getPropertyName()) and
|
|
// Exclude obvious array access
|
|
astNode.getPropertyNameExpr().analyze().getAType() = TTString()
|
|
}
|
|
|
|
/** Gets the base of the dynamic read. */
|
|
DataFlow::Node getBase() { result = astNode.getBase().flow() }
|
|
|
|
/** Gets the node holding the name of the property. */
|
|
DataFlow::Node getPropertyNameNode() { result = astNode.getIndex().flow() }
|
|
|
|
/**
|
|
* Holds if the value of this read was assigned to earlier in the same basic block.
|
|
*
|
|
* For example, this is true for `dst[x]` on line 2 below:
|
|
* ```js
|
|
* dst[x] = {};
|
|
* dst[x][y] = src[y];
|
|
* ```
|
|
*/
|
|
predicate hasDominatingAssignment() { AccessPath::DominatingPaths::hasDominatingWrite(this) }
|
|
}
|
|
|
|
/**
|
|
* Holds if `output` is the result of `base[key]`, either directly or through
|
|
* one or more function calls, ignoring reads that can't access the prototype chain.
|
|
*/
|
|
predicate dynamicPropReadStep(Node base, Node key, SourceNode output) {
|
|
exists(DynamicPropRead read |
|
|
not read.hasDominatingAssignment() and
|
|
base = read.getBase() and
|
|
key = read.getPropertyNameNode() and
|
|
output = read
|
|
)
|
|
or
|
|
// Summarize functions returning a dynamic property read of two parameters, such as `function getProp(obj, prop) { return obj[prop]; }`.
|
|
exists(
|
|
CallNode call, Function callee, ParameterNode baseParam, ParameterNode keyParam, Node innerBase,
|
|
Node innerKey, SourceNode innerOutput
|
|
|
|
|
dynamicPropReadStep(innerBase, innerKey, innerOutput) and
|
|
baseParam.flowsTo(innerBase) and
|
|
keyParam.flowsTo(innerKey) and
|
|
innerOutput.flowsTo(callee.getAReturnedExpr().flow()) and
|
|
call.getACallee() = callee and
|
|
argumentPassingStep(call, base, callee, baseParam) and
|
|
argumentPassingStep(call, key, callee, keyParam) and
|
|
output = call
|
|
)
|
|
}
|