mirror of
https://github.com/github/codeql.git
synced 2026-01-12 06:00:23 +01:00
98 lines
3.2 KiB
Plaintext
98 lines
3.2 KiB
Plaintext
/**
|
|
* Provides classes for the local objects that the dataflow library can reason about soundly.
|
|
*/
|
|
|
|
import javascript
|
|
|
|
/**
|
|
* Holds if the dataflow library can not track flow through `escape` due to `cause`.
|
|
*/
|
|
private predicate isEscape(DataFlow::Node escape, string cause) {
|
|
escape = any(DataFlow::InvokeNode invk).getAnArgument() and cause = "argument"
|
|
or
|
|
escape = any(DataFlow::FunctionNode fun).getAReturn() and cause = "return"
|
|
or
|
|
escape = any(YieldExpr yield).getOperand().flow() and cause = "yield"
|
|
or
|
|
escape = any(ThrowStmt t).getExpr().flow() and cause = "throw"
|
|
or
|
|
escape = any(GlobalVariable v).getAnAssignedExpr().flow() and cause = "global"
|
|
or
|
|
escape = any(DataFlow::PropWrite write).getRhs() and cause = "heap"
|
|
or
|
|
escape = any(ExportDeclaration e).getSourceNode(_) and cause = "export"
|
|
or
|
|
exists(WithStmt with, Assignment assign |
|
|
with.mayAffect(assign.getLhs()) and
|
|
assign.getRhs().flow() = escape and
|
|
cause = "heap"
|
|
)
|
|
}
|
|
|
|
private DataFlow::Node getAnEscape() { isEscape(result, _) }
|
|
|
|
/**
|
|
* Holds if `n` can flow to a `this`-variable.
|
|
*/
|
|
private predicate exposedAsReceiver(DataFlow::SourceNode n) {
|
|
// pragmatic limitation: guarantee for object literals only
|
|
not n instanceof DataFlow::ObjectLiteralNode
|
|
or
|
|
exists(AbstractValue v | n.getAPropertyWrite().getRhs().analyze().getALocalValue() = v |
|
|
v.isIndefinite(_) or
|
|
exists(ThisExpr dis | dis.getBinder() = v.(AbstractCallable).getFunction())
|
|
)
|
|
or
|
|
n.flowsToExpr(any(FunctionBindExpr bind).getObject())
|
|
or
|
|
// technically, the builtin prototypes could have a `this`-using function through which this node escapes, but we ignore that here
|
|
// (we also ignore `o['__' + 'proto__'] = ...`)
|
|
exists(n.getAPropertyWrite("__proto__"))
|
|
or
|
|
// could check the assigned value of all affected variables, but it is unlikely to matter in practice
|
|
exists(WithStmt with | n.flowsToExpr(with.getExpr()))
|
|
}
|
|
|
|
/**
|
|
* An object that is entirely local, in the sense that the dataflow
|
|
* library models all of its flow.
|
|
*
|
|
* All uses of this node are modeled by `this.flowsTo(_)` and related predicates.
|
|
*/
|
|
class LocalObject extends DataFlow::SourceNode {
|
|
LocalObject() {
|
|
// pragmatic limitation: object literals only
|
|
this instanceof DataFlow::ObjectLiteralNode and
|
|
not this.flowsTo(getAnEscape()) and
|
|
not exposedAsReceiver(this)
|
|
}
|
|
|
|
pragma[nomagic]
|
|
predicate hasOwnProperty(string name) {
|
|
// the property is defined in the initializer,
|
|
any(DataFlow::PropWrite write).writes(this, name, _) and
|
|
// and it is never deleted
|
|
not this.hasDeleteWithName(name) and
|
|
// and there is no deleted property with computed name
|
|
not this.hasDeleteWithComputedProperty()
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate hasDeleteWithName(string name) {
|
|
exists(DeleteExpr del, DataFlow::PropRef ref |
|
|
del.getOperand().flow() = ref and
|
|
this.flowsTo(ref.getBase()) and
|
|
ref.getPropertyName() = name
|
|
)
|
|
}
|
|
|
|
pragma[noinline]
|
|
private predicate hasDeleteWithComputedProperty() {
|
|
exists(DeleteExpr del, DataFlow::PropRef ref |
|
|
del.getOperand().flow() = ref and
|
|
this.flowsTo(ref.getBase()) and
|
|
not exists(ref.getPropertyName())
|
|
)
|
|
}
|
|
}
|