mirror of
https://github.com/github/codeql.git
synced 2026-04-30 19:26:02 +02:00
JS: Add GlobalAccessPaths library
This commit is contained in:
@@ -25,6 +25,7 @@ import semmle.javascript.Extend
|
||||
import semmle.javascript.Externs
|
||||
import semmle.javascript.Files
|
||||
import semmle.javascript.Functions
|
||||
import semmle.javascript.GlobalAccessPaths
|
||||
import semmle.javascript.HTML
|
||||
import semmle.javascript.HtmlSanitizers
|
||||
import semmle.javascript.JSDoc
|
||||
|
||||
179
javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll
Normal file
179
javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll
Normal file
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* Provides predicates for associating qualified names with data flow nodes.
|
||||
*/
|
||||
import javascript
|
||||
|
||||
module GlobalAccessPath {
|
||||
/**
|
||||
* A local variable with exactly one definition, not counting implicit initialization.
|
||||
*/
|
||||
private class EffectivelyConstantVariable extends LocalVariable {
|
||||
EffectivelyConstantVariable() {
|
||||
strictcount(SsaExplicitDefinition ssa | ssa.getSourceVariable() = this) = 1
|
||||
}
|
||||
|
||||
/** Gets the SSA definition of this variable. */
|
||||
SsaExplicitDefinition getSsaDefinition() {
|
||||
result.getSourceVariable() = this
|
||||
}
|
||||
|
||||
/** Gets the data flow node representing the value of this variable, if one exists. */
|
||||
DataFlow::Node getValue() {
|
||||
result = getSsaDefinition().getRhsNode()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the global access path referred to by `node`.
|
||||
*
|
||||
* This holds for direct references as well as for aliases
|
||||
* established through local data flow.
|
||||
*
|
||||
* Examples:
|
||||
* ```
|
||||
* 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 || {});
|
||||
* ```
|
||||
*/
|
||||
string fromReference(DataFlow::Node node) {
|
||||
result = fromReference(node.getImmediatePredecessor())
|
||||
or
|
||||
exists(EffectivelyConstantVariable var |
|
||||
var.isCaptured() and
|
||||
node.asExpr() = var.getAnAccess() and
|
||||
result = fromReference(var.getValue())
|
||||
)
|
||||
or
|
||||
node.accessesGlobal(result) and
|
||||
result != "undefined"
|
||||
or
|
||||
not node.accessesGlobal(_) and
|
||||
exists(DataFlow::PropRead prop | node = prop |
|
||||
result = fromReference(prop.getBase()) + "." + prop.getPropertyName()
|
||||
)
|
||||
or
|
||||
exists(Closure::ClosureNamespaceAccess acc | node = acc |
|
||||
result = acc.getClosureNamespace()
|
||||
)
|
||||
or
|
||||
exists(DataFlow::PropertyProjection proj | node = proj |
|
||||
proj.isSingletonProjection() and
|
||||
result = fromReference(proj.getObject()) + "." + proj.getASelector()
|
||||
)
|
||||
or
|
||||
// Treat 'e || {}' as having the same name as 'e'
|
||||
exists(LogOrExpr e | node.asExpr() = e |
|
||||
e.getRightOperand().(ObjectExpr).getNumProperty() = 0 and
|
||||
result = fromReference(e.getLeftOperand().flow())
|
||||
)
|
||||
or
|
||||
// Treat 'e && e.f' as having the same name as 'e.f'
|
||||
exists(LogAndExpr e, Expr lhs, PropAccess rhs | node.asExpr() = e |
|
||||
lhs = e.getLeftOperand() and
|
||||
rhs = e.getRightOperand() and
|
||||
(
|
||||
exists(Variable v |
|
||||
lhs = v.getAnAccess() and
|
||||
rhs.getBase() = v.getAnAccess()
|
||||
)
|
||||
or
|
||||
exists(string name |
|
||||
lhs.(PropAccess).getQualifiedName() = name and
|
||||
rhs.getBase().(PropAccess).getQualifiedName() = name
|
||||
)
|
||||
) and
|
||||
result = fromReference(rhs.flow())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `rhs` is the right-hand side of a self-assignment.
|
||||
*
|
||||
* This usually happens in defensive initialization, for example:
|
||||
* ```
|
||||
* foo = foo || {};
|
||||
* ```
|
||||
*/
|
||||
predicate isSelfAssignment(DataFlow::Node rhs) {
|
||||
fromRhs(rhs) = fromReference(rhs)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if there is an assignment to `accessPath` in `file`, not counting
|
||||
* self-assignments.
|
||||
*/
|
||||
predicate isAssignedInFile(string accessPath, File file) {
|
||||
exists(DataFlow::Node rhs |
|
||||
fromRhs(rhs) = accessPath and
|
||||
not isSelfAssignment(rhs) and
|
||||
rhs.getFile() = file
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `accessPath` is only assigned to from one file, not counting
|
||||
* self-assignments.
|
||||
*/
|
||||
predicate isAssignedInUniqueFile(string accessPath) {
|
||||
strictcount(File f | isAssignedInFile(accessPath, f)) = 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the global access path `node` is being assigned to, if any.
|
||||
*
|
||||
* Only holds for the immediate right-hand side of an assignment or property, not
|
||||
* for nodes that transitively flow there.
|
||||
*
|
||||
* For example, the class nodes below all map to `foo.bar`:
|
||||
* ```
|
||||
* foo.bar = class {};
|
||||
*
|
||||
* foo = { bar: class {} };
|
||||
*
|
||||
* (function(f) {
|
||||
* f.bar = class {}
|
||||
* })(foo = foo || {});
|
||||
* ```
|
||||
*/
|
||||
string fromRhs(DataFlow::Node node) {
|
||||
exists(DataFlow::SourceNode base, string baseName, string name |
|
||||
node = base.getAPropertyWrite(name).getRhs() and
|
||||
result = baseName + "." + name
|
||||
|
|
||||
baseName = fromReference(base)
|
||||
or
|
||||
baseName = fromRhs(base)
|
||||
)
|
||||
or
|
||||
exists(AssignExpr assign |
|
||||
node = assign.getRhs().flow() and
|
||||
result = assign.getLhs().(GlobalVarAccess).getName()
|
||||
)
|
||||
or
|
||||
exists(FunctionDeclStmt fun |
|
||||
node = DataFlow::valueNode(fun) and
|
||||
result = fun.getId().(GlobalVarDecl).getName()
|
||||
)
|
||||
or
|
||||
exists(ClassDeclStmt cls |
|
||||
node = DataFlow::valueNode(cls) and
|
||||
result = cls.getIdentifier().(GlobalVarDecl).getName()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the global access path referenced by or assigned to `node`.
|
||||
*/
|
||||
string getAccessPath(DataFlow::Node node) {
|
||||
result = fromReference(node)
|
||||
or
|
||||
not exists(fromReference(node)) and
|
||||
result = fromRhs(node)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
test_fromReference
|
||||
| other_ns.js:2:11:2:12 | ns | NS |
|
||||
| other_ns.js:3:3:3:4 | ns | NS |
|
||||
| other_ns.js:3:3:3:8 | ns.foo | NS.foo |
|
||||
| other_ns.js:3:3:3:12 | ns.foo.bar | NS.foo.bar |
|
||||
| other_ns.js:4:4:4:5 | NS | NS |
|
||||
| other_ns.js:4:4:4:16 | NS = NS \|\| {} | NS |
|
||||
| other_ns.js:4:9:4:10 | NS | NS |
|
||||
| other_ns.js:4:9:4:16 | NS \|\| {} | NS |
|
||||
| other_ns.js:6:1:6:8 | Conflict | Conflict |
|
||||
| test.js:2:7:2:17 | v | foo.bar |
|
||||
| test.js:2:11:2:13 | foo | foo |
|
||||
| test.js:2:11:2:17 | foo.bar | foo.bar |
|
||||
| test.js:3:3:3:3 | v | foo.bar |
|
||||
| test.js:3:3:3:7 | v.baz | foo.bar.baz |
|
||||
| test.js:4:7:4:24 | { baz, a, b: {c} } | foo.bar |
|
||||
| test.js:4:7:4:28 | c | foo.bar.b.c |
|
||||
| test.js:4:9:4:11 | baz | foo.bar.baz |
|
||||
| test.js:4:14:4:14 | a | foo.bar.a |
|
||||
| test.js:4:17:4:22 | b: {c} | foo.bar.b |
|
||||
| test.js:4:20:4:22 | {c} | foo.bar.b |
|
||||
| test.js:4:21:4:21 | c | foo.bar.b.c |
|
||||
| test.js:4:28:4:28 | v | foo.bar |
|
||||
| test.js:5:11:5:11 | c | foo.bar.b.c |
|
||||
| test.js:5:11:5:13 | c.d | foo.bar.b.c.d |
|
||||
| test.js:7:7:7:16 | w | window |
|
||||
| test.js:7:11:7:16 | window | window |
|
||||
| test.js:8:13:8:18 | window | window |
|
||||
| test.js:8:13:8:20 | window.x | x |
|
||||
| test.js:8:13:8:22 | window.x.y | x.y |
|
||||
| test.js:9:13:9:18 | global | global |
|
||||
| test.js:9:13:9:20 | global.x | x |
|
||||
| test.js:9:13:9:22 | global.x.y | x.y |
|
||||
| test.js:10:13:10:13 | w | window |
|
||||
| test.js:10:13:10:15 | w.x | x |
|
||||
| test.js:10:13:10:17 | w.x.y | x.y |
|
||||
| test.js:12:7:12:25 | notUnique | foo.bar |
|
||||
| test.js:12:19:12:21 | foo | foo |
|
||||
| test.js:12:19:12:25 | foo.bar | foo.bar |
|
||||
| test.js:13:7:13:15 | something | something |
|
||||
| test.js:14:5:14:23 | notUnique | bar.baz |
|
||||
| test.js:14:17:14:19 | bar | bar |
|
||||
| test.js:14:17:14:23 | bar.baz | bar.baz |
|
||||
| test.js:22:11:22:12 | ns | NS |
|
||||
| test.js:23:3:23:4 | ns | NS |
|
||||
| test.js:23:3:23:8 | ns.foo | NS.foo |
|
||||
| test.js:23:3:23:12 | ns.foo.bar | NS.foo.bar |
|
||||
| test.js:24:4:24:5 | NS | NS |
|
||||
| test.js:24:4:24:16 | NS = NS \|\| {} | NS |
|
||||
| test.js:24:9:24:10 | NS | NS |
|
||||
| test.js:24:9:24:16 | NS \|\| {} | NS |
|
||||
| test.js:26:1:26:8 | Conflict | Conflict |
|
||||
test_fromRhs
|
||||
| other_ns.js:4:9:4:16 | NS \|\| {} | NS |
|
||||
| other_ns.js:6:12:6:13 | {} | Conflict |
|
||||
| test.js:1:1:20:1 | functio ... ss {}\\n} | f |
|
||||
| test.js:24:9:24:16 | NS \|\| {} | NS |
|
||||
| test.js:26:12:26:13 | {} | Conflict |
|
||||
| test.js:28:1:28:20 | class GlobalClass {} | GlobalClass |
|
||||
| test.js:30:1:30:28 | functio ... on() {} | globalFunction |
|
||||
test_assignedUnique
|
||||
| GlobalClass |
|
||||
| f |
|
||||
| globalFunction |
|
||||
@@ -0,0 +1,13 @@
|
||||
import javascript
|
||||
|
||||
query string test_fromReference(DataFlow::Node node) {
|
||||
result = GlobalAccessPath::fromReference(node)
|
||||
}
|
||||
|
||||
query string test_fromRhs(DataFlow::Node node) {
|
||||
result = GlobalAccessPath::fromRhs(node)
|
||||
}
|
||||
|
||||
query string test_assignedUnique() {
|
||||
GlobalAccessPath::isAssignedInUniqueFile(result)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// use NS from another file, that doesn't truly assign to it
|
||||
(function(ns) {
|
||||
ns.foo.bar; // 'NS.foo.bar'
|
||||
})(NS = NS || {});
|
||||
|
||||
Conflict = {}; // assigned in multiple files
|
||||
30
javascript/ql/test/library-tests/GlobalAccessPaths/test.js
Normal file
30
javascript/ql/test/library-tests/GlobalAccessPaths/test.js
Normal file
@@ -0,0 +1,30 @@
|
||||
function f() {
|
||||
let v = foo.bar; // 'foo.bar'
|
||||
v.baz; // 'foo.bar.baz'
|
||||
let { baz, a, b: {c} } = v;
|
||||
let d = c.d; // 'foo.bar.b.c.d'
|
||||
|
||||
let w = window;
|
||||
let xy1 = window.x.y; // 'x.y'
|
||||
let xy2 = global.x.y; // 'x.y'
|
||||
let xy3 = w.x.y; // 'x.y'
|
||||
|
||||
let notUnique = foo.bar;
|
||||
if (something()) {
|
||||
notUnique = bar.baz;
|
||||
}
|
||||
notUnique.x; // No global access path
|
||||
|
||||
function localFunction() {}
|
||||
class LocalClass {}
|
||||
}
|
||||
|
||||
(function(ns) {
|
||||
ns.foo.bar; // 'NS.foo.bar'
|
||||
})(NS = NS || {});
|
||||
|
||||
Conflict = {}; // assigned in multiple files
|
||||
|
||||
class GlobalClass {}
|
||||
|
||||
function globalFunction() {}
|
||||
Reference in New Issue
Block a user