Merge remote-tracking branch 'upstream/master' into ExceptionalPromise

This commit is contained in:
Erik Krogh Kristensen
2020-01-20 09:20:09 +01:00
153 changed files with 24516 additions and 8448 deletions

View File

@@ -31,6 +31,7 @@
+ semmlecode-javascript-queries/Security/CWE-352/MissingCsrfMiddleware.ql: /Security/CWE/CWE-352
+ semmlecode-javascript-queries/Security/CWE-400/RemotePropertyInjection.ql: /Security/CWE/CWE-400
+ semmlecode-javascript-queries/Security/CWE-400/PrototypePollution.ql: /Security/CWE/CWE-400
+ semmlecode-javascript-queries/Security/CWE-400/PrototypePollutionUtility.ql: /Security/CWE/CWE-400
+ semmlecode-javascript-queries/Security/CWE-502/UnsafeDeserialization.ql: /Security/CWE/CWE-502
+ semmlecode-javascript-queries/Security/CWE-506/HardcodedDataInterpretedAsCode.ql: /Security/CWE/CWE-506
+ semmlecode-javascript-queries/Security/CWE-601/ClientSideUrlRedirect.ql: /Security/CWE/CWE-601

View File

@@ -156,5 +156,7 @@ predicate hasNoEffect(Expr e) {
not exists(fe.getName())
) and
// exclude block-level flow type annotations. For example: `(name: empty)`.
not e.(ParExpr).getExpression().getLastToken().getNextToken().getValue() = ":"
not e.(ParExpr).getExpression().getLastToken().getNextToken().getValue() = ":" and
// exclude the first statement of a try block
not e = any(TryStmt stmt).getBody().getStmt(0).(ExprStmt).getExpr()
}

View File

@@ -32,5 +32,6 @@ where
acc.accesses(baseNode.asExpr(), prop) and
acc.getContainer().isStrict() and
illegalPropAccess(baseNode.getAValue(), base, prop) and
forex(AbstractValue av | av = baseNode.getAValue() | illegalPropAccess(av, _, prop))
forex(AbstractValue av | av = baseNode.getAValue() | illegalPropAccess(av, _, prop)) and
not acc = any(ExprStmt stmt).getExpr() // reported by js/useless-expression
select acc, "Strict mode code cannot use " + base + "." + prop + "."

View File

@@ -0,0 +1,71 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>
Most JavaScript objects inherit the properties of the built-in <code>Object.prototype</code> object.
Prototype pollution is a type of vulnerability in which an attacker is able to modify <code>Object.prototype</code>.
Since most objects inherit from the compromised <code>Object.prototype</code>, the attacker can use this
to tamper with the application logic, and often escalate to remote code execution or cross-site scripting.
</p>
<p>
One way to cause prototype pollution is through use of an unsafe <em>merge</em> or <em>extend</em> function
to recursively copy properties from one object to another.
Such a function has the potential to modify any object reachable from the destination object, and
the built-in <code>Object.prototype</code> is usually reachable through the special properties
<code>__proto__</code> and <code>constructor.prototype</code>.
</p>
</overview>
<recommendation>
<p>
The most effective place to guard against this is in the function that performs
the recursive copy.
</p>
<p>
Only merge a property recursively when it is an own property of the <em>destination</em> object.
Alternatively, blacklist the property names <code>__proto__</code> and <code>constructor</code>
from being merged.
</p>
</recommendation>
<example>
<p>
This function recursively copies properties from <code>src</code> to <code>dst</code>:
</p>
<sample src="examples/PrototypePollutionUtility.js"/>
<p>
However, if <code>src</code> is the object <code>{"__proto__": {"isAdmin": true}}</code>,
it will inject the property <code>isAdmin: true</code> in <code>Object.prototype</code>.
</p>
<p>
The issue can be fixed by ensuring that only own properties of the destination object
are merged recursively:
</p>
<sample src="examples/PrototypePollutionUtility_fixed.js"/>
<p>
Alternatively, blacklist the <code>__proto__</code> and <code>constructor</code> properties:
</p>
<sample src="examples/PrototypePollutionUtility_fixed2.js"/>
</example>
<references>
<li>Prototype pollution attacks:
<a href="https://hackerone.com/reports/380873">lodash</a>,
<a href="https://hackerone.com/reports/454365">jQuery</a>,
<a href="https://hackerone.com/reports/381185">extend</a>,
<a href="https://hackerone.com/reports/430291">just-extend</a>,
<a href="https://hackerone.com/reports/381194">merge.recursive</a>.
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,566 @@
/**
* @name Prototype pollution in utility function
* @description Recursively copying properties between objects may cause
accidental modification of a built-in prototype object.
* @kind path-problem
* @problem.severity warning
* @precision high
* @id js/prototype-pollution-utility
* @tags security
* external/cwe/cwe-400
* external/cwe/cwe-471
*/
import javascript
import DataFlow
import PathGraph
import semmle.javascript.dataflow.InferredTypes
/**
* 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" or name = "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 object whose properties are being enumerated.
*
* For example, gets `src` in `for (var key in src)`.
*/
abstract DataFlow::Node getSourceObject();
/**
* Gets a local reference of the source object.
*/
SourceNode getASourceObjectRef() {
exists(SourceNode root, string path |
getSourceObject() = AccessPath::getAReferenceTo(root, path) and
result = AccessPath::getAReferenceTo(root, path)
)
or
result = getSourceObject().getALocalSource()
}
/**
* 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]; }`.
*/
PropRead getASourceProp() {
result = getASourceObjectRef().getAPropertyRead() and
result.getPropertyNameExpr().flow().getImmediatePredecessor*() = this
}
}
/**
* Property enumeration through `for-in` for `Object.keys` or similar.
*/
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`.
*/
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 PropRead getASourceProp() {
result = super.getASourceProp()
or
result = entry.getAPropertyRead("1")
}
}
/**
* Holds if the properties of `node` are enumerated locally.
*/
predicate arePropertiesEnumerated(DataFlow::SourceNode node) {
node = any(EnumeratedPropName name).getASourceObjectRef()
}
/**
* 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() }
/**
* 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() {
exists(DataFlow::PropWrite write, BasicBlock bb, int i, int j, SsaVariable ssaVar |
write = getBase().getALocalSource().getAPropertyWrite() and
bb.getNode(i) = write.getWriteNode() and
bb.getNode(j) = astNode and
i < j and
write.getPropertyNameExpr() = ssaVar.getAUse() and
astNode.getIndex() = ssaVar.getAUse()
)
}
}
/**
* Holds if there is a dynamic property assignment of form `base[prop] = rhs`
* which might act as the writing operation in a recursive merge function.
*
* Only assignments to pre-existing objects are of interest, so object/array literals
* are not included.
*
* Additionally, we ignore cases where the properties of `base` are enumerated, as this
* would typically not happen in a merge function.
*/
predicate dynamicPropWrite(DataFlow::Node base, DataFlow::Node prop, DataFlow::Node rhs) {
exists(AssignExpr write, IndexExpr index |
index = write.getLhs() and
base = index.getBase().flow() and
prop = index.getPropertyNameExpr().flow() and
rhs = write.getRhs().flow() and
not exists(prop.getStringValue()) and
not arePropertiesEnumerated(base.getALocalSource())
)
}
/** Gets the name of a property that can lead to `Object.prototype`. */
string unsafePropName() {
result = "__proto__"
or
result = "constructor"
}
/**
* Flow label representing an unsafe property name, or an object obtained
* by using such a property in a dynamic read.
*/
class UnsafePropLabel extends FlowLabel {
UnsafePropLabel() { this = unsafePropName() }
}
/**
* Tracks data from property enumerations to dynamic property writes.
*
* The intent is to find code of the general form:
* ```js
* function merge(dst, src) {
* for (var key in src)
* if (...)
* merge(dst[key], src[key])
* else
* dst[key] = src[key]
* }
* ```
*
* This configuration is used to find three separate data flow paths originating
* from a property enumeration, all leading to the same dynamic property write.
*
* In particular, the base and property name of the property write should all
* depend on the enumerated property name (`key`) and the right-hand side should
* depend on the source property (`src[key]`), while allowing steps of form
* `x -> x[p]` and `p -> x[p]`.
*
* Note that in the above example, the flow from `key` to the base of the write (`dst`)
* requires stepping through the recursive call.
* Such a path would be absent for a shallow copying operation, where the `dst` object
* isn't derived from a property of the source object.
*
* This configuration can't enforce that all three paths must end at the same
* dynamic property write, so we treat the paths independently here and check
* for coinciding paths afterwards. This means this configuration can't be used as
* a standalone configuration like in most path queries.
*/
class PropNameTracking extends DataFlow::Configuration {
PropNameTracking() { this = "PropNameTracking" }
override predicate isSource(DataFlow::Node node, FlowLabel label) {
label instanceof UnsafePropLabel and
exists(EnumeratedPropName prop |
node = prop
or
node = prop.getASourceProp()
)
}
override predicate isSink(DataFlow::Node node, FlowLabel label) {
label instanceof UnsafePropLabel and
(
dynamicPropWrite(node, _, _) or
dynamicPropWrite(_, node, _) or
dynamicPropWrite(_, _, node)
)
}
override predicate isAdditionalFlowStep(
DataFlow::Node pred, DataFlow::Node succ, FlowLabel predlbl, FlowLabel succlbl
) {
predlbl instanceof UnsafePropLabel and
succlbl = predlbl and
(
// Step through `p -> x[p]`
exists(PropRead read |
pred = read.getPropertyNameExpr().flow() and
not read.(DynamicPropRead).hasDominatingAssignment() and
succ = read
)
or
// Step through `x -> x[p]`
exists(DynamicPropRead read |
not read.hasDominatingAssignment() and
pred = read.getBase() and
succ = read
)
)
}
override predicate isBarrier(DataFlow::Node node) {
super.isBarrier(node)
or
exists(ConditionGuardNode guard, SsaRefinementNode refinement |
node = DataFlow::ssaDefinitionNode(refinement) and
refinement.getGuard() = guard and
guard.getTest() instanceof VarAccess and
guard.getOutcome() = false
)
}
override predicate isBarrierGuard(DataFlow::BarrierGuardNode node) {
node instanceof BlacklistEqualityGuard or
node instanceof WhitelistEqualityGuard or
node instanceof HasOwnPropertyGuard or
node instanceof InExprGuard or
node instanceof InstanceOfGuard or
node instanceof TypeofGuard or
node instanceof BlacklistInclusionGuard or
node instanceof WhitelistInclusionGuard
}
}
/**
* Sanitizer guard of form `x === "__proto__"` or `x === "constructor"`.
*/
class BlacklistEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNode {
override EqualityTest astNode;
string propName;
BlacklistEqualityGuard() {
astNode.getAnOperand().getStringValue() = propName and
propName = unsafePropName()
}
override predicate blocks(boolean outcome, Expr e, FlowLabel label) {
e = astNode.getAnOperand() and
outcome = astNode.getPolarity().booleanNot() and
label = propName
}
}
/**
* An equality test with something other than `__proto__` or `constructor`.
*/
class WhitelistEqualityGuard extends DataFlow::LabeledBarrierGuardNode, ValueNode {
override EqualityTest astNode;
WhitelistEqualityGuard() {
not astNode.getAnOperand().getStringValue() = unsafePropName() and
astNode.getAnOperand() instanceof Literal
}
override predicate blocks(boolean outcome, Expr e, FlowLabel label) {
e = astNode.getAnOperand() and
outcome = astNode.getPolarity() and
label instanceof UnsafePropLabel
}
}
/**
* Sanitizer guard for calls to `Object.prototype.hasOwnProperty`.
*
* A malicious source object will have `__proto__` and/or `constructor` as own properties,
* but the destination object generally doesn't. It is therefore only a sanitizer when
* used on the destination object.
*/
class HasOwnPropertyGuard extends DataFlow::BarrierGuardNode, CallNode {
HasOwnPropertyGuard() {
// Make sure we handle reflective calls since libraries love to do that.
getCalleeNode().getALocalSource().(DataFlow::PropRead).getPropertyName() = "hasOwnProperty" and
exists(getReceiver()) and
// Try to avoid `src.hasOwnProperty` by requiring that the receiver
// does not locally have its properties enumerated. Typically there is no
// reason to enumerate the properties of the destination object.
not arePropertiesEnumerated(getReceiver().getALocalSource())
}
override predicate blocks(boolean outcome, Expr e) {
e = getArgument(0).asExpr() and outcome = true
}
}
/**
* Sanitizer guard for `key in dst`.
*
* Since `"__proto__" in obj` and `"constructor" in obj` is true for most objects,
* this is seen as a sanitizer for `key` in the false outcome.
*/
class InExprGuard extends DataFlow::BarrierGuardNode, DataFlow::ValueNode {
override InExpr astNode;
InExprGuard() {
// Exclude tests of form `key in src` for the same reason as in HasOwnPropertyGuard
not arePropertiesEnumerated(astNode.getRightOperand().flow().getALocalSource())
}
override predicate blocks(boolean outcome, Expr e) {
e = astNode.getLeftOperand() and outcome = false
}
}
/**
* Sanitizer guard for `instanceof` expressions.
*
* `Object.prototype instanceof X` is never true, so this blocks the `__proto__` label.
*
* It is still possible to get to `Function.prototype` through `constructor.constructor.prototype`
* so we do not block the `constructor` label.
*/
class InstanceOfGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::ValueNode {
override InstanceOfExpr astNode;
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
e = astNode.getLeftOperand() and outcome = true and label = "__proto__"
}
}
/**
* Sanitizer guard of form `typeof x === "object"` or `typeof x === "function"`.
*
* The former blocks the `constructor` label as that payload must pass through a function,
* and the latter blocks the `__proto__` label as that only passes through objects.
*/
class TypeofGuard extends DataFlow::LabeledBarrierGuardNode, DataFlow::ValueNode {
override EqualityTest astNode;
TypeofExpr typeof;
string typeofStr;
TypeofGuard() {
typeof = astNode.getAnOperand() and
typeofStr = astNode.getAnOperand().getStringValue()
}
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
e = typeof.getOperand() and
outcome = astNode.getPolarity() and
(
typeofStr = "object" and
label = "constructor"
or
typeofStr = "function" and
label = "__proto__"
)
or
e = typeof.getOperand() and
outcome = astNode.getPolarity().booleanNot() and
(
// If something is not an object, sanitize object, as both must end
// in non-function prototype object.
typeofStr = "object" and
label instanceof UnsafePropLabel
or
typeofStr = "function" and
label = "constructor"
)
}
}
/**
* A check of form `["__proto__"].includes(x)` or similar.
*/
class BlacklistInclusionGuard extends DataFlow::LabeledBarrierGuardNode, InclusionTest {
UnsafePropLabel label;
BlacklistInclusionGuard() {
exists(DataFlow::ArrayCreationNode array |
array.getAnElement().getStringValue() = label and
array.flowsTo(getContainerNode())
)
}
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel lbl) {
outcome = getPolarity().booleanNot() and
e = getContainedNode().asExpr() and
label = lbl
}
}
/**
* A check of form `xs.includes(x)` or similar, which sanitizes `x` in the true case.
*/
class WhitelistInclusionGuard extends DataFlow::LabeledBarrierGuardNode {
WhitelistInclusionGuard() {
this instanceof TaintTracking::PositiveIndexOfSanitizer or
this instanceof TaintTracking::InclusionSanitizer
}
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel lbl) {
this.(TaintTracking::AdditionalSanitizerGuardNode).sanitizes(outcome, e) and
lbl instanceof UnsafePropLabel
}
}
/**
* Gets a meaningful name for `node` if possible.
*/
string getExprName(DataFlow::Node node) {
result = node.asExpr().(Identifier).getName()
or
result = node.asExpr().(DotExpr).getPropertyName()
}
/**
* Gets a name to display for `node`.
*/
string deriveExprName(DataFlow::Node node) {
result = getExprName(node)
or
not exists(getExprName(node)) and
result = "this object"
}
/**
* Holds if the dynamic property write `base[prop] = rhs` can pollute the prototype
* of `base` due to flow from `enum`.
*
* In most cases this will result in an alert, the exception being the case where
* `base` does not have a prototype at all.
*/
predicate isPrototypePollutingAssignment(Node base, Node prop, Node rhs, EnumeratedPropName enum) {
dynamicPropWrite(base, prop, rhs) and
exists(PropNameTracking cfg |
cfg.hasFlow(enum, base) and
cfg.hasFlow(enum, prop) and
cfg.hasFlow(enum.getASourceProp(), rhs)
)
}
/** Gets a data flow node leading to the base of a prototype-polluting assignment. */
private DataFlow::SourceNode getANodeLeadingToBase(DataFlow::TypeBackTracker t, Node base) {
t.start() and
isPrototypePollutingAssignment(base, _, _, _) and
result = base.getALocalSource()
or
exists(DataFlow::TypeBackTracker t2 |
result = getANodeLeadingToBase(t2, base).backtrack(t2, t)
)
}
/**
* Gets a data flow node leading to the base of dynamic property read leading to a
* prototype-polluting assignment.
*
* For example, this is the `dst` in `dst[key1][key2] = ...`.
* This dynamic read is where the reference to a built-in prototype object is obtained,
* and we need this to ensure that this object actually has a prototype.
*/
private DataFlow::SourceNode getANodeLeadingToBaseBase(DataFlow::TypeBackTracker t, Node base) {
exists(DynamicPropRead read |
read = getANodeLeadingToBase(t, base) and
result = read.getBase().getALocalSource()
)
or
exists(DataFlow::TypeBackTracker t2 |
result = getANodeLeadingToBaseBase(t2, base).backtrack(t2, t)
)
}
DataFlow::SourceNode getANodeLeadingToBaseBase(Node base) {
result = getANodeLeadingToBaseBase(DataFlow::TypeBackTracker::end(), base)
}
/** A call to `Object.create(null)`. */
class ObjectCreateNullCall extends CallNode {
ObjectCreateNullCall() {
this = globalVarRef("Object").getAMemberCall("create") and
getArgument(0).asExpr() instanceof NullLiteral
}
}
from
PropNameTracking cfg, DataFlow::PathNode source, DataFlow::PathNode sink, EnumeratedPropName enum,
Node base
where
cfg.hasFlowPath(source, sink) and
isPrototypePollutingAssignment(base, _, _, enum) and
sink.getNode() = base and
source.getNode() = enum and
(
getANodeLeadingToBaseBase(base) instanceof ObjectLiteralNode
or
not getANodeLeadingToBaseBase(base) instanceof ObjectCreateNullCall
)
select base, source, sink,
"Properties are copied from $@ to $@ without guarding against prototype pollution.",
enum.getSourceObject(), deriveExprName(enum.getSourceObject()), base, deriveExprName(base)

View File

@@ -0,0 +1,10 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}

View File

@@ -0,0 +1,10 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (dst.hasOwnProperty(key) && isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}

View File

@@ -0,0 +1,11 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (key === "__proto__" || key === "constructor") continue;
if (isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}

View File

@@ -73,6 +73,7 @@ import semmle.javascript.frameworks.Credentials
import semmle.javascript.frameworks.CryptoLibraries
import semmle.javascript.frameworks.DigitalOcean
import semmle.javascript.frameworks.Electron
import semmle.javascript.frameworks.EventEmitter
import semmle.javascript.frameworks.Files
import semmle.javascript.frameworks.Firebase
import semmle.javascript.frameworks.jQuery

View File

@@ -3,6 +3,7 @@
*/
import javascript
private import semmle.javascript.dataflow.InferredTypes
deprecated
module GlobalAccessPath {
@@ -34,6 +35,24 @@ module GlobalAccessPath {
}
}
/**
* Provides predicates for associating access paths with data flow nodes.
*
* For example, `AccessPath.getAReferenceTo(x)` can be used to obtain the global access path
* that `x` refers to, as in the following sample:
* ```
* function f() {
* let v = foo.bar; // reference to 'foo.bar'
* v.baz; // reference to 'foo.bar.baz'
* }
*
* (function(ns) {
* ns.x; // reference to 'NS.x'
* })(NS = NS || {});
* ```
*
* A pseudo-property named `[number]` is sometimes used to represent array indices within an access path.
*/
module AccessPath {
/**
* A source node that can be the root of an access path.
@@ -80,6 +99,35 @@ module AccessPath {
result = base + "." + prop
}
/**
* Holds if `variable` is compared to the `length` property of something, indicating
* that, if used as a dynamic property name, it represents an array index.
*/
private predicate isLikelyArrayIndex(SsaVariable variable) {
exists(RelationalComparison cmp, DataFlow::PropRead length, Expr lengthUse |
length.getPropertyName() = "length" and
length.flowsToExpr(lengthUse) and
cmp.hasOperands(variable.getAUse(), lengthUse)
)
or
isLikelyArrayIndex(variable.getDefinition().(SsaRefinementNode).getAnInput())
}
/**
* Holds if `prop` likely accesses a non-constant array element.
*/
private predicate isLikelyDynamicArrayAccess(DataFlow::PropRead prop) {
// The implicit PropRead in a for-of loop is represented by its lvalue node
prop = DataFlow::lvalueNode(any(ForOfStmt stmt).getLValue())
or
// Match an index access x[i] where `i` is likely an array index variable.
not exists(prop.getPropertyName()) and
exists(SsaVariable indexVar |
isLikelyArrayIndex(indexVar) and
prop.getPropertyNameExpr() = indexVar.getAUse()
)
}
/**
* Gets the access path relative to `root` referred to by `node`.
*
@@ -115,6 +163,9 @@ module AccessPath {
not node.accessesGlobal(_) and
exists(DataFlow::PropRead prop | node = prop |
result = join(fromReference(prop.getBase(), root), prop.getPropertyName())
or
isLikelyDynamicArrayAccess(prop) and
result = join(fromReference(prop.getBase(), root), "[number]")
)
or
exists(Closure::ClosureNamespaceAccess acc | node = acc |

View File

@@ -73,187 +73,109 @@ module Electron {
/**
* A reference to the `webContents` property of a browser object.
*/
class WebContents extends DataFlow::SourceNode {
class WebContents extends DataFlow::SourceNode, NodeJSLib::NodeJSEventEmitter {
WebContents() { this.(DataFlow::PropRead).accesses(any(BrowserObject bo), "webContents") }
}
/**
* Provides classes and predicates for modelling Electron inter-process communication (IPC).
* The Electron IPC are EventEmitters, but they also expose a number of methods on top of the standard EventEmitter.
*/
private module IPC {
class Process extends string {
Process() { this = "main" or this = "renderer" }
DataFlow::SourceNode main() { result = DataFlow::moduleMember("electron", "ipcMain") }
DataFlow::SourceNode getAnImport() {
this = Process::main() and result = DataFlow::moduleMember("electron", "ipcMain")
DataFlow::SourceNode renderer() { result = DataFlow::moduleMember("electron", "ipcRenderer") }
/**
* A model for the Main and Renderer process in an Electron app.
*/
abstract class Process extends EventEmitter::Range {
/**
* Gets a node that refers to a Process object.
*/
DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) }
}
/**
* An instance of the Main process of an Electron app.
* Communication in an Electron app generally happens from the renderer process to the main process.
*/
class MainProcess extends Process {
MainProcess() { this = main() }
}
/**
* An instance of the renderer process of an Electron app.
*/
class RendererProcess extends Process {
RendererProcess() { this = renderer() }
}
/**
* The `sender` property of the event in an IPC event handler.
* This sender is used to send a response back from the main process to the renderer.
*/
class ProcessSender extends Process {
ProcessSender() {
exists(IPCSendRegistration reg | reg.getEmitter() instanceof MainProcess |
this = reg.getABoundCallbackParameter(1, 0).getAPropertyRead("sender")
)
}
}
/**
* A registration of an Electron IPC event handler.
* Does mostly the same as an EventEmitter event handler,
* except that values can be returned through the `event.returnValue` property.
*/
class IPCSendRegistration extends EventRegistration::DefaultEventRegistration,
DataFlow::MethodCallNode {
override Process emitter;
IPCSendRegistration() { this = emitter.ref().getAMethodCall(EventEmitter::on()) }
override DataFlow::Node getAReturnedValue() {
result = this.getABoundCallbackParameter(1, 0).getAPropertyWrite("returnValue").getRhs()
}
override IPCDispatch getAReturnDispatch() {
result.getCalleeName() = "sendSync"
}
}
/**
* A dispatch of an IPC event.
* An IPC event is sent from the renderer to the main process.
* And a value can be returned through the `returnValue` property of the event (first parameter in the callback).
*/
class IPCDispatch extends EventDispatch::DefaultEventDispatch, DataFlow::InvokeNode {
override Process emitter;
IPCDispatch() {
exists(string methodName | methodName = "sendSync" or methodName = "send" |
this = emitter.ref().getAMemberCall(methodName)
)
}
/**
* Gets the `i`th dispatched argument to the event handler.
* The 0th parameter in the callback is a event generated by the IPC system,
* therefore these arguments start at 1.
*/
override DataFlow::Node getSentItem(int i) {
i >= 1 and
result = getArgument(i)
}
/**
* Gets a registration that this dispatch can send an event to.
*/
override IPCSendRegistration getAReceiver() {
this.getEmitter() instanceof RendererProcess and
result.getEmitter() instanceof MainProcess
or
this = Process::renderer() and result = DataFlow::moduleMember("electron", "ipcRenderer")
}
}
module Process {
Process main() { result = "main" }
Process renderer() { result = "renderer" }
}
/**
* An IPC callback.
*/
class Callback extends DataFlow::FunctionNode {
DataFlow::Node channel;
Process process;
Callback() {
exists(DataFlow::MethodCallNode mc |
mc = process.getAnImport().getAMemberCall("on") and
this = mc.getCallback(1) and
channel = mc.getArgument(0)
)
}
/** Gets the process on which this callback is executed. */
Process getProcess() { result = process }
/** Gets the name of the channel the callback is listening on. */
string getChannelName() { result = channel.getStringValue() }
/** Gets the data flow node containing the message received by the callback. */
DataFlow::Node getMessage() { result = getParameter(1) }
}
/**
* An IPC message.
*/
abstract class Message extends DataFlow::Node {
/** Gets the process that sends this message. */
abstract Process getProcess();
/** Gets the name of the channel this message is sent on. */
abstract string getChannelName();
}
/**
* An IPC message sent directly from a process.
*/
class DirectMessage extends Message {
DataFlow::MethodCallNode mc;
Process process;
DataFlow::Node channel;
boolean isSync;
DirectMessage() {
exists(string send |
send = "send" and isSync = false
or
send = "sendSync" and isSync = true
|
mc = process.getAnImport().getAMemberCall(send) and
this = mc.getArgument(1) and
channel = mc.getArgument(0)
)
}
override Process getProcess() { result = process }
override string getChannelName() { result = channel.getStringValue() }
}
/**
* A synchronous IPC message sent directly from a process.
*/
class SyncDirectMessage extends DirectMessage {
SyncDirectMessage() { isSync = true }
/** Gets the data flow node holding the reply to the message. */
DataFlow::Node getReply() { result = mc }
}
/**
* An asynchronous IPC reply sent from within an IPC callback.
*/
class AsyncReplyMessage extends Message {
Callback callback;
DataFlow::Node channel;
AsyncReplyMessage() {
exists(DataFlow::MethodCallNode mc |
mc = callback.getParameter(0).getAPropertyRead("sender").getAMemberCall("send") and
this = mc.getArgument(1) and
channel = mc.getArgument(0)
)
}
override Process getProcess() { result = callback.getProcess() }
override string getChannelName() { result = channel.getStringValue() }
}
/**
* A synchronous IPC reply sent from within an IPC callback.
*/
class SyncReplyMessage extends Message {
Callback callback;
SyncReplyMessage() {
this = callback.getParameter(0).getAPropertyWrite("returnValue").getRhs()
}
override Process getProcess() { result = callback.getProcess() }
override string getChannelName() { result = callback.getChannelName() }
}
/**
* An asynchronous Electron IPC message sent from the main process via a `webContents` object.
*/
class WebContentsMessage extends Message {
DataFlow::Node channel;
WebContentsMessage() {
exists(WebContents wc, DataFlow::MethodCallNode mc |
wc.flowsTo(mc.getReceiver()) and
this = mc.getArgument(1) and
channel = mc.getArgument(0) and
mc.getCalleeName() = "send"
)
}
override Process getProcess() { result = Process::main() }
override string getChannelName() { result = channel.getStringValue() }
}
/**
* Holds if `pred` flows to `succ` via Electron IPC.
*/
private predicate ipcFlowStep(DataFlow::Node pred, DataFlow::Node succ) {
// match a message sent from one process with a callback parameter in the other process
exists(Callback callback, Message msg |
callback.getChannelName() = msg.getChannelName() and
callback.getProcess() != msg.getProcess() and
pred = msg and
succ = callback.getMessage()
)
or
// match a synchronous reply sent from one process with a `sendSync` call in the other process
exists(SyncDirectMessage sendSync, SyncReplyMessage msg |
sendSync.getChannelName() = msg.getChannelName() and
sendSync.getProcess() != msg.getProcess() and
pred = msg and
succ = sendSync.getReply()
)
}
/**
* An additional flow step via an Electron IPC message.
*/
private class IPCAdditionalFlowStep extends DataFlow::AdditionalFlowStep {
IPCAdditionalFlowStep() { ipcFlowStep(this, _) }
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
pred = this and
ipcFlowStep(pred, succ)
this.getEmitter() instanceof ProcessSender and
result.getEmitter() instanceof RendererProcess
}
}
}

View File

@@ -0,0 +1,229 @@
import javascript
module EventEmitter {
/** Gets the name of a method on `EventEmitter` that returns `this`. */
string chainableMethod() {
result = "off" or
result = "removeAllListeners" or
result = "removeListener" or
result = "setMaxListeners" or
result = on()
}
/** Gets the name of a method on `EventEmitter` that registers an event handler. */
string on() {
result = "addListener" or
result = "on" or
result = "once" or
result = "prependListener" or
result = "prependOnceListener"
}
/**
* Gets a node that refers to an EventEmitter object.
*/
DataFlow::SourceNode trackEventEmitter(EventEmitter emitter) {
result = trackEventEmitter(DataFlow::TypeTracker::end(), emitter)
}
private DataFlow::SourceNode trackEventEmitter(DataFlow::TypeTracker t, EventEmitter emitter) {
t.start() and result = emitter
or
exists(DataFlow::TypeTracker t2, DataFlow::SourceNode pred |
pred = trackEventEmitter(t2, emitter)
|
result = pred.track(t2, t)
or
// invocation of a chainable method
exists(DataFlow::MethodCallNode mcn |
mcn = pred.getAMethodCall(chainableMethod()) and
// exclude getter versions
exists(mcn.getAnArgument()) and
result = mcn and
t = t2.continue()
)
)
}
/**
* An object that implements the EventEmitter API.
* Extending this class does nothing, its mostly to indicate intent.
*
* The classes EventRegistration::Range and EventDispatch::Range must be extended to get a working EventEmitter model.
* An EventRegistration models a method call that registers some event handler on an EventEmitter.
* And EventDispatch models that some event is dispatched on an EventEmitter.
*
* Both the EventRegistration and EventDispatch have a field `emitter`,
* which is the EventEmitter that events are registered on / dispatched to respectively.
*
* Here is a simple JavaScript example with the NodeJS EventEmitter:
* var e = new EventEmitter(); // <- EventEmitter
* e.on("name", (data) => {...}); // <- EventRegistration
* e.emit("name", "foo"); // <- EventDispatch
*/
abstract class Range extends DataFlow::Node { }
}
/**
* An EventEmitter instance that implements the EventEmitter API.
* Extend EventEmitter::Range to mark something as being an EventEmitter.
*/
class EventEmitter extends DataFlow::Node {
EventEmitter::Range range;
EventEmitter() { this = range }
}
/**
* A registration of an event handler on an EventEmitter.
*/
class EventRegistration extends DataFlow::Node {
EventRegistration::Range range;
EventRegistration() { this = range }
/** Gets the EventEmitter that the event handler is registered on. */
final EventEmitter getEmitter() { result = range.getEmitter() }
/** Gets the name of the channel if possible. */
string getChannel() { result = range.getChannel() }
/** Gets the `i`th parameter in the event handler. */
DataFlow::Node getReceivedItem(int i) { result = range.getReceivedItem(i) }
/**
* Gets a value that is returned by the event handler.
* The default implementation is that no value can be returned.
*/
DataFlow::Node getAReturnedValue() { result = range.getAReturnedValue() }
/**
* Get a dispatch that this event handler can return a value to.
* The default implementation is that there exists no such dispatch.
*/
EventDispatch getAReturnDispatch() { result = range.getAReturnDispatch() }
}
module EventRegistration {
/**
* A registration of an event handler on an EventEmitter.
*/
abstract class Range extends DataFlow::Node {
EventEmitter::Range emitter;
final EventEmitter getEmitter() { result = emitter }
abstract string getChannel();
abstract DataFlow::Node getReceivedItem(int i);
abstract DataFlow::Node getAReturnedValue();
abstract EventDispatch::Range getAReturnDispatch();
}
/**
* A default implementation of an EventRegistration.
* The default implementation assumes that `this` is a DataFlow::InvokeNode where the
* first argument is a string describing which channel is registered, and the second
* argument is the event handler callback.
*/
abstract class DefaultEventRegistration extends Range, DataFlow::InvokeNode {
override string getChannel() {
this.getArgument(0).mayHaveStringValue(result)
}
override DataFlow::Node getReceivedItem(int i) {
result = this.getABoundCallbackParameter(1, i)
}
override DataFlow::Node getAReturnedValue() { none() }
override EventDispatch::Range getAReturnDispatch() { none() }
}
}
/**
* A dispatch of an event on an EventEmitter.
*/
class EventDispatch extends DataFlow::Node {
EventDispatch::Range range;
EventDispatch() { this = range }
/** Gets the emitter that the event dispatch happens on. */
EventEmitter getEmitter() { result = range.getEmitter() }
/** Gets the name of the channel if possible. */
string getChannel() { result = range.getChannel() }
/** Gets the `i`th argument that is send to the event handler. */
DataFlow::Node getSentItem(int i) { result = range.getSentItem(i) }
/**
* Get an EventRegistration that this event dispatch can send an event to.
* The default implementation is that the emitters of the dispatch and registration have to be equal.
* Channels are by default ignored.
*/
EventRegistration getAReceiver() { result = range.getAReceiver() }
}
module EventDispatch {
/**
* A dispatch of an event on an EventEmitter.
*/
abstract class Range extends DataFlow::Node {
EventEmitter::Range emitter;
final EventEmitter getEmitter() { result = emitter }
abstract string getChannel();
abstract DataFlow::Node getSentItem(int i);
abstract EventRegistration::Range getAReceiver();
}
/**
* A default implementation of an EventDispatch.
* The default implementation assumes that the dispatch is a DataFlow::InvokeNode,
* where the first argument is a string describing the channel, and the `i`+1 argument
* is the `i`th item sent to the event handler.
*/
abstract class DefaultEventDispatch extends Range, DataFlow::InvokeNode {
override string getChannel() {
this.getArgument(0).mayHaveStringValue(result)
}
override DataFlow::Node getSentItem(int i) {
result = this.getArgument(i + 1)
}
override EventRegistration::Range getAReceiver() { this.getEmitter() = result.getEmitter() }
}
}
/**
* A taint-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()
}
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
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
}
}

View File

@@ -880,4 +880,49 @@ module NodeJSLib {
override string getSourceType() { result = "NodeJSClientRequest error event" }
}
/**
* An NodeJS EventEmitter instance.
* Events dispatched on this EventEmitter will be handled by event handlers registered on this EventEmitter.
* (That is opposed to e.g. SocketIO, which implements the same interface, but where events cross object boundaries).
*/
abstract class NodeJSEventEmitter extends EventEmitter::Range {
/**
* Get a Node that refers to a NodeJS EventEmitter instance.
*/
DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) }
}
/**
* An instance of an EventEmitter that is imported through the 'events' module.
*/
private class ImportedNodeJSEventEmitter extends NodeJSEventEmitter {
ImportedNodeJSEventEmitter() {
exists(DataFlow::SourceNode clazz |
clazz = DataFlow::moduleImport("events") or
clazz = DataFlow::moduleMember("events", "EventEmitter")
|
this = clazz.getAnInstantiation()
)
}
}
/**
* A registration of an event handler on a NodeJS EventEmitter instance.
*/
private class NodeJSEventRegistration extends EventRegistration::DefaultEventRegistration, DataFlow::MethodCallNode {
override NodeJSEventEmitter emitter;
NodeJSEventRegistration() { this = emitter.ref().getAMethodCall(EventEmitter::on()) }
}
/**
* A dispatch of an event on a NodeJS EventEmitter instance.
*/
private class NodeJSEventDispatch extends EventDispatch::DefaultEventDispatch, DataFlow::MethodCallNode {
override NodeJSEventEmitter emitter;
NodeJSEventDispatch() { this = emitter.ref().getAMethodCall("emit") }
}
}

View File

@@ -574,27 +574,6 @@ module SocketIOClient {
}
}
/** Provides predicates for working with Node.js `EventEmitter`s. */
private module EventEmitter {
/** Gets the name of a method on `EventEmitter` that returns `this`. */
string chainableMethod() {
result = "off" or
result = "removeAllListeners" or
result = "removeListener" or
result = "setMaxListeners" or
result = on()
}
/** Gets the name of a method on `EventEmitter` that registers an event handler. */
string on() {
result = "addListener" or
result = "on" or
result = "once" or
result = "prependListener" or
result = "prependOnceListener"
}
}
/** A data flow step through socket.io sockets. */
private class SocketIoStep extends DataFlow::AdditionalFlowStep {
DataFlow::Node pred;

View File

@@ -10,6 +10,10 @@
| electron.js:39:5:39:6 | bw |
| electron.js:40:1:40:7 | foo(bv) |
| electron.js:40:5:40:6 | bv |
| electron.js:62:7:62:59 | win |
| electron.js:62:13:62:59 | new Bro ... 1500 }) |
| electron.js:63:3:63:5 | win |
| electron.js:65:18:65:20 | win |
| electron.ts:3:12:3:13 | bw |
| electron.ts:3:40:3:41 | bv |
| electron.ts:4:3:4:4 | bw |

View File

@@ -2,3 +2,5 @@
| electron.js:48:23:48:28 | 'pong' | electron.js:58:1:58:36 | ipcRend ... 'ping') |
| electron.js:56:27:56:32 | 'ping' | electron.js:42:29:42:31 | arg |
| electron.js:58:30:58:35 | 'ping' | electron.js:47:28:47:30 | arg |
| electron.js:68:24:68:28 | "foo" | electron.js:67:23:67:25 | foo |
| electron.js:69:24:69:28 | "bar" | electron.js:67:46:67:48 | bar |

View File

@@ -1,4 +1,5 @@
| electron.js:39:1:39:19 | foo(bw).webContents |
| electron.js:40:1:40:19 | foo(bv).webContents |
| electron.js:65:18:65:32 | win.webContents |
| electron.ts:4:3:4:16 | bw.webContents |
| electron.ts:5:3:5:16 | bv.webContents |

View File

@@ -56,3 +56,15 @@ ipcRenderer.on('reply', (event, arg) => {
ipcRenderer.send('async', 'ping');
ipcRenderer.sendSync('sync', 'ping');
(function () {
let win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('http://github.com');
let contents = win.webContents;
contents.on("foo", (foo) => {}).on("bar", (bar) => {});
contents.emit("foo", "foo");
contents.emit("bar", "bar");
})();

View File

@@ -0,0 +1,6 @@
| tst.js:9:23:9:33 | 'FirstData' | tst.js:6:40:6:44 | first |
| tst.js:10:24:10:35 | 'SecondData' | tst.js:7:32:7:37 | second |
| tst.js:15:24:15:39 | 'OtherFirstData' | tst.js:14:41:14:50 | otherFirst |
| tst.js:20:17:20:21 | "foo" | tst.js:19:16:19:18 | foo |
| tst.js:21:17:21:21 | "bar" | tst.js:19:39:19:41 | bar |
| tst.js:28:17:28:22 | "blab" | tst.js:25:16:25:20 | event |

View File

@@ -0,0 +1,8 @@
import javascript
query predicate taintSteps(DataFlow::Node pred, DataFlow::Node succ) {
exists(DataFlow::AdditionalFlowStep step |
step.step(pred, succ)
)
}

View File

@@ -0,0 +1,28 @@
var emitter = require('events').EventEmitter;
var em = new emitter();
// Splitting different channels
em.addListener('FirstEvent', function (first) {});
em.on('SecondEvent', function (second) {});
em.emit('FirstEvent', 'FirstData');
em.emit('SecondEvent', 'SecondData');
// Splitting different emitters.
var em2 = new emitter();
em2.addListener('FirstEvent', function (otherFirst) {});
em2.emit('FirstEvent', 'OtherFirstData');
// Chaining.
var em3 = new emitter();
em3.on("foo", (foo) => {}).on("bar", (bar) => {});
em3.emit("foo", "foo");
em3.emit("bar", "bar");
// Returning a value does not work here.
var em4 = new emitter();
em3.on("bla", (event) => {
event.returnValue = "foo"
});
em3.emit("bla", "blab");

View File

@@ -1,3 +1,4 @@
| try.js:22:9:22:26 | x.ordinaryProperty | This expression has no effect. |
| tst2.js:3:4:3:4 | 0 | This expression has no effect. |
| tst.js:3:1:3:2 | 23 | This expression has no effect. |
| tst.js:5:1:5:2 | 23 | This expression has no effect. |

View File

@@ -0,0 +1,27 @@
function try1(x) {
try {
x.ordinaryProperty; // OK - try/catch indicates intent to throw exception
} catch (e) {
return false;
}
return true;
}
function try2(x) {
try {
x.ordinaryProperty; // OK - try/catch indicates intent to throw exception
return x;
} catch (e) {
return false;
}
}
function try3(x) {
try {
x.ordinaryProperty()
x.ordinaryProperty // NOT OK
return x;
} catch (e) {
return false;
}
}

View File

@@ -1,7 +1,7 @@
| tst.js:5:30:5:45 | arguments.callee | Strict mode code cannot use arguments.callee. |
| tst.js:7:21:7:36 | arguments.callee | Strict mode code cannot use arguments.callee. |
| tst.js:9:20:9:27 | f.caller | Strict mode code cannot use Function.prototype.caller. |
| tst.js:11:8:11:18 | f.arguments | Strict mode code cannot use Function.prototype.arguments. |
| tst.js:18:3:18:18 | arguments.callee | Strict mode code cannot use arguments.callee. |
| tst.js:31:5:31:14 | foo.caller | Strict mode code cannot use Function.prototype.caller. |
| tst.js:31:5:31:14 | foo.caller | Strict mode code cannot use arguments.caller. |
| tst.js:11:17:11:27 | f.arguments | Strict mode code cannot use Function.prototype.arguments. |
| tst.js:18:10:18:25 | arguments.callee | Strict mode code cannot use arguments.callee. |
| tst.js:31:12:31:21 | foo.caller | Strict mode code cannot use Function.prototype.caller. |
| tst.js:31:12:31:21 | foo.caller | Strict mode code cannot use arguments.caller. |

View File

@@ -8,14 +8,14 @@ var o = {
// BAD
console.log(f.caller);
// BAD
f.arguments;
this.y = f.arguments;
this.x = x;
}
};
var D = class extends function() {
// BAD
arguments.callee;
return arguments.callee;
} {};
function g() {
@@ -28,6 +28,11 @@ function g() {
function h() {
var foo = Math.random() > 0.5 ? h : arguments;
// BAD
foo.caller;
return foo.caller;
}
})();
})();
(function() {
'use strict';
arguments.caller; // OK - avoid duplicate alert from useless-expression
})();

View File

@@ -0,0 +1 @@
Security/CWE-400/PrototypePollutionUtility.ql

View File

@@ -0,0 +1,362 @@
import dummy from 'somewhere';
function copyUsingForIn(dst, src) {
for (let key in src) {
if (dst[key]) {
copyUsingForIn(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
function copyUsingKeys(dst, src) {
Object.keys(src).forEach(key => {
if (dst[key]) {
copyUsingKeys(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
});
}
function copyRest(dst, ...sources) {
for (let source of sources) {
for (let key in source) {
copyRestAux(dst, source[key], key);
}
}
}
function copyRestAux(dst, value, key) {
let dstValue = dst[key];
if (dstValue) {
copyRest(dstValue, value);
} else {
dst[key] = value; // NOT OK
}
}
function copyProtoGuarded(dst, src) {
for (let key in src) {
if (key === "__proto__") continue;
if (dst[key]) {
copyProtoGuarded(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
function copyCtorGuarded(dst, src) {
for (let key in src) {
if (key === "constructor") continue;
if (dst[key]) {
copyCtorGuarded(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
function copyDoubleGuarded(dst, src) {
for (let key in src) {
if (key === "constructor" || key === "__proto__") continue;
if (dst[key]) {
copyDoubleGuarded(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
function isSafe(key) {
return key !== "__proto__" && key !== "constructor" && key !== "prototype";
}
function copyComplex(dst, src) {
for (let key in src) {
if (isSafe(key)) {
if (dst[key]) {
copyComplex(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
}
function copyHasOwnProperty(dst, src) {
for (let key in src) {
// Guarding the recursive case by dst.hasOwnProperty is safe,
// since '__proto__' and 'constructor' are not own properties of the destination object.
if (dst.hasOwnProperty(key)) {
copyHasOwnProperty(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
function copyHasOwnPropertyBad(dst, src) {
for (let key in src) {
// Guarding using src.hasOwnProperty is *not* effective,
// since '__proto__' and 'constructor' are own properties in the payload.
if (!src.hasOwnProperty(key)) continue; // Not safe
if (dst[key]) {
copyHasOwnPropertyBad(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
let _hasOwnProp = Object.prototype.hasOwnProperty;
function copyHasOwnPropertyTearOff(dst, src) {
for (let key in src) {
if (_hasOwnProp.call(dst, key)) {
copyHasOwnPropertyTearOff(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
function shallowExtend(dst, src) {
for (let key in src) {
dst[key] = src[key]; // OK
}
}
function transform(src, fn) {
if (typeof src !== 'object') return fn(src);
for (let key in src) {
src[key] = transform(src[key], fn); // OK
}
return src;
}
function clone(src) {
if (typeof src !== 'object') return src;
let result = {};
for (let key in src) {
result[key] = clone(src[key]); // OK
}
return result;
}
function higherOrderRecursion(dst, src, callback) {
for (let key in src) {
if (dst[key]) {
callback(dst, src, key);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
function higherOrderRecursionEntry(dst, src) {
higherOrderRecursion(dst, src, (dst, src, key) => {
higherOrderRecursionEntry(dst[key], src[key]);
});
}
function instanceofObjectGuard(dst, src) {
for (let key in src) {
let dstValue = dst[key];
if (typeof dstValue === 'object' && dstValue instanceof Object) {
instanceofObjectGuard(dstValue, src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
let blacklist = ["__proto__", "constructor"];
function copyWithBlacklist(dst, src) {
for (let key in src) {
if (blacklist.indexOf(key) >= 0) continue;
if (dst[key]) {
copyWithBlacklist(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
function copyUsingPlainForLoop(dst, src) {
let keys = Object.keys(src);
for (let i = 0; i < keys.length; ++i) {
let key = keys[i];
if (dst[key]) {
copyUsingPlainForLoop(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
}
}
function copyUsingPlainForLoopNoAlias(dst, src) {
// Like copyUsingPlainForLoop, but with keys[i] duplicated at every use-site
let keys = Object.keys(src);
for (let i = 0; i < keys.length; ++i) {
if (dst[key]) {
copyUsingPlainForLoopNoAlias(dst[keys[i]], src[keys[i]]);
} else {
dst[keys[i]] = src[keys[i]]; // NOT OK - but not flagged
}
}
}
function deepSet(map, key1, key2, value) {
if (!map[key1]) {
map[key1] = Object.create(null);
}
map[key1][key2] = value; // OK
}
function deepSetCaller(data) {
let map1 = Object.create(null);
let map2 = Object.create(null);
for (let key in data) {
deepSet(map1, key, 'x', data[key]);
deepSet(map2, 'x', key, data[key]);
}
}
function deepSetBad(map, key1, key2, value) {
if (!map[key1]) {
map[key1] = Object.create(null);
}
map[key1][key2] = value; // NOT OK - object literal can flow here
}
function deepSetCallerBad(data) {
let map1 = Object.create(null);
for (let key in data) {
deepSetBad({}, key, 'x', data[key]); // oops
deepSetBad(map1, 'x', key, data[key]);
}
}
function maybeCopy(x) {
if (x && typeof x === 'object') {
return {...x};
} else {
return x;
}
}
function mergeWithCopy(dst, src) {
if (dst == null) return src;
let result = maybeCopy(dst);
for (let key in src) {
if (src.hasOwnProperty(key)) {
result[key] = mergeWithCopy(dst[key], src[key]); // OK
}
}
return result;
}
function copyUsingEntries(dst, src) {
Object.entries(src).forEach(entry => {
let key = entry[0];
let value = entry[1];
if (dst[key]) {
copyUsingEntries(dst[key], value);
} else {
dst[key] = value; // NOT OK
}
});
}
function copyUsingReflect(dst, src) {
Reflect.ownKeys(src).forEach(key => {
if (dst[key]) {
copyUsingReflect(dst[key], src[key]);
} else {
dst[key] = src[key]; // NOT OK
}
});
}
function copyWithPath(dst, src, path) {
for (let key in src) {
if (src.hasOwnProperty(key)) {
if (dst[key]) {
copyWithPath(dst[key], src[key], path ? path + '.' + key : key);
} else {
let target = {};
target[path] = {};
target[path][key] = src[key]; // OK
doSomething(target);
}
}
}
return dst;
}
function typeofObjectTest(dst, src) {
for (let key in src) {
if (src.hasOwnProperty(key)) {
let value = src[key];
if (dst[key] && typeof value === 'object') {
typeofObjectTest(dst[key], value);
} else {
dst[key] = value; // NOT OK
}
}
}
}
function mergeRephinementNode(dst, src) {
for (let key in src) {
if (src.hasOwnProperty(key)) {
if (key === key && key === key) continue; // Create a phi-node of refinement nodes
let value = src[key];
if (dst[key] && typeof value === 'object') {
mergeRephinementNode(dst[key], value);
} else {
dst[key] = value; // NOT OK
}
}
}
}
function mergeSelective(dst, src) {
for (let key in src) {
if (src.hasOwnProperty(key)) {
// Only 'prefs' is merged recursively
if (key in dst && key !== 'prefs') {
continue;
}
if (dst[key]) {
mergeSelective(dst[key], src[key]);
} else {
dst[key] = src[key]; // OK
}
}
}
}
function isNonArrayObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
function mergePlainObjectsOnly(target, source) {
if (isNonArrayObject(target) && isNonArrayObject(source)) {
Object.keys(source).forEach(key => {
if (key === '__proto__') {
return;
}
if (isNonArrayObject(source[key]) && key in target) {
target[key] = mergePlainObjectsOnly(target[key], source[key], options);
} else {
target[key] = source[key]; // OK
}
});
}
return target;
}

View File

@@ -0,0 +1,10 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}

View File

@@ -0,0 +1,10 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (dst.hasOwnProperty(key) && isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}

View File

@@ -0,0 +1,11 @@
function merge(dst, src) {
for (let key in src) {
if (!src.hasOwnProperty(key)) continue;
if (key === "__proto__" || key === "constructor") continue;
if (isObject(dst[key])) {
merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
}