Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions javascript/ql/src/semmle/javascript/AST.qll
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class ASTNode extends @ast_node, NodeInStmtContainer {
int getNumChildStmt() { result = count(getAChildStmt()) }

/** Gets the parent node of this node, if any. */
cached
ASTNode getParent() { this = result.getAChild() }

/** Gets the first control flow node belonging to this syntactic entity. */
Expand Down
5 changes: 2 additions & 3 deletions javascript/ql/src/semmle/javascript/Arrays.qll
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,8 @@ private module ArrayDataFlow {

ArrayIndexingStep() {
read = this and
forex(InferredType type | type = read.getPropertyNameExpr().flow().analyze().getAType() |
type = TTNumber()
) and
TTNumber() =
unique(InferredType type | type = read.getPropertyNameExpr().flow().analyze().getAType()) and
exists(VarAccess i, ExprOrVarDecl init |
i = read.getPropertyNameExpr() and init = any(ForStmt f).getInit()
|
Expand Down
20 changes: 16 additions & 4 deletions javascript/ql/src/semmle/javascript/dataflow/Configuration.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1296,12 +1296,9 @@ private predicate summarizedHigherOrderCall(
Function f, DataFlow::InvokeNode outer, DataFlow::InvokeNode inner, int j,
DataFlow::Node innerArg, DataFlow::SourceNode cbParm, PathSummary oldSummary
|
reachableFromInput(f, outer, arg, innerArg, cfg, oldSummary) and
// Only track actual parameter flow.
// Captured flow does not need to be summarized - it is handled by the local case in `higherOrderCall`.
not arg = DataFlow::capturedVariableNode(_) and
argumentPassing(outer, cb, f, cbParm) and
innerArg = inner.getArgument(j)
summarizedHigherOrderCallAux(f, outer, arg, innerArg, cfg, oldSummary, cbParm, inner, j, cb)
|
// direct higher-order call
cbParm.flowsTo(inner.getCalleeNode()) and
Expand All @@ -1317,6 +1314,21 @@ private predicate summarizedHigherOrderCall(
)
}

/**
* @see `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
) {
reachableFromInput(f, outer, arg, innerArg, cfg, oldSummary) and
// Only track actual parameter flow.
argumentPassing(outer, cb, f, cbParm) and
innerArg = inner.getArgument(j)
}

/**
* Holds if `arg` is passed as the `i`th argument to `callback` through a callback invocation.
*
Expand Down
32 changes: 22 additions & 10 deletions javascript/ql/src/semmle/javascript/dataflow/Nodes.qll
Original file line number Diff line number Diff line change
Expand Up @@ -365,22 +365,38 @@ DataFlow::SourceNode globalObjectRef() {
)
or
// DOM
result = globalVarRef("window")
result = globalVariable("window")
or
// Node.js
result = globalVarRef("global")
result = globalVariable("global")
or
// DOM and service workers
result = globalVarRef("self")
result = globalVariable("self")
or
// ECMAScript 2020
result = globalVarRef("globalThis")
result = globalVariable("globalThis")
or
// `require("global")`
result = moduleImport("global")
or
// Closure library - based on AST to avoid recursion with Closure library model
result = globalVarRef("goog").getAPropertyRead("global")
result = globalVariable("goog").getAPropertyRead("global")
}

/**
* Gets a reference to a global variable `name`.
* For example, if `name` is "foo":
* ```js
* foo
* require('global/foo')
* ```
*/
private DataFlow::SourceNode globalVariable(string name) {
result.(GlobalVarRefNode).getName() = name
or
// `require("global/document")` or `require("global/window")`
(name = "document" or name = "window") and
result = moduleImport("global/" + name)
}

/**
Expand All @@ -398,13 +414,9 @@ DataFlow::SourceNode globalObjectRef() {
*/
pragma[nomagic]
DataFlow::SourceNode globalVarRef(string name) {
result.(GlobalVarRefNode).getName() = name
result = globalVariable(name)
or
result = globalObjectRef().getAPropertyReference(name)
or
// `require("global/document")` or `require("global/window")`
(name = "document" or name = "window") and
result = moduleImport("global/" + name)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ class VarRefinementContext extends RefinementContext, TVarRefinementContext {
}

/** Holds if `e` is nested inside a guard node. */
pragma[nomagic]
private predicate inGuard(Expr e) {
e = any(GuardControlFlowNode g).getTest()
or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,53 @@ module PropertyProjection {

deprecated class CustomPropertyProjection = PropertyProjection::Range;

/**
* Gets a callee of a simple property projection call.
* This predicate is used exclusively in `SimplePropertyProjection`.
*/
pragma[noinline]
private DataFlow::SourceNode getASimplePropertyProjectionCallee(
boolean singleton, int selectorIndex, int objectIndex
) {
singleton = false and
(
result = LodashUnderscore::member("pick") and
objectIndex = 0 and
selectorIndex = [1 .. max(result.getACall().getNumArgument())]
or
result = LodashUnderscore::member("pickBy") and
objectIndex = 0 and
selectorIndex = 1
or
result = DataFlow::moduleMember("ramda", ["pick", "pickAll", "pickBy"]) and
objectIndex = 1 and
selectorIndex = 0
or
result = DataFlow::moduleMember("dotty", "search") and
objectIndex = 0 and
selectorIndex = 1
)
or
singleton = true and
(
result = LodashUnderscore::member("get") and
objectIndex = 0 and
selectorIndex = 1
or
result = DataFlow::moduleMember("ramda", "path") and
objectIndex = 1 and
selectorIndex = 0
or
result = DataFlow::moduleMember("dottie", "get") and
objectIndex = 0 and
selectorIndex = 1
or
result = DataFlow::moduleMember("dotty", "get") and
objectIndex = 0 and
selectorIndex = 1
)
}

/**
* A simple model of common property projection functions.
*/
Expand All @@ -74,51 +121,7 @@ private class SimplePropertyProjection extends PropertyProjection::Range {
boolean singleton;

SimplePropertyProjection() {
exists(DataFlow::SourceNode callee | this = callee.getACall() |
singleton = false and
(
callee = LodashUnderscore::member("pick") and
objectIndex = 0 and
selectorIndex = [1 .. getNumArgument()]
or
callee = LodashUnderscore::member("pickBy") and
objectIndex = 0 and
selectorIndex = 1
or
exists(string name |
name = "pick" or
name = "pickAll" or
name = "pickBy"
|
callee = DataFlow::moduleMember("ramda", name) and
objectIndex = 1 and
selectorIndex = 0
)
or
callee = DataFlow::moduleMember("dotty", "search") and
objectIndex = 0 and
selectorIndex = 1
)
or
singleton = true and
(
callee = LodashUnderscore::member("get") and
objectIndex = 0 and
selectorIndex = 1
or
callee = DataFlow::moduleMember("ramda", "path") and
objectIndex = 1 and
selectorIndex = 0
or
callee = DataFlow::moduleMember("dottie", "get") and
objectIndex = 0 and
selectorIndex = 1
or
callee = DataFlow::moduleMember("dotty", "get") and
objectIndex = 0 and
selectorIndex = 1
)
)
this = getASimplePropertyProjectionCallee(singleton, selectorIndex, objectIndex).getACall()
}

override DataFlow::Node getObject() { result = getArgument(objectIndex) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,29 @@

import javascript

private predicate execApi(string mod, string fn, int cmdArg, int optionsArg, boolean shell) {
mod = "cross-spawn" and
fn = "sync" and
cmdArg = 0 and
shell = false and
optionsArg = -1
or
mod = "execa" and
optionsArg = -1 and
pragma[noinline]
private predicate execApi(
string mod, string fn, int cmdArg, int optionsArg, boolean shell, boolean sync
) {
sync = getSync(fn) and
(
mod = "cross-spawn" and
fn = "sync" and
cmdArg = 0 and
shell = false and
(
fn = "node" or
fn = "stdout" or
fn = "stderr" or
fn = "sync"
)
optionsArg = -1
or
shell = true and
mod = "execa" and
optionsArg = -1 and
(
fn = "command" or
fn = "commandSync" or
fn = "shell" or
fn = "shellSync"
)
) and
cmdArg = 0
shell = false and
fn = ["node", "stdout", "stderr", "sync"]
or
shell = true and
fn = ["command", "commandSync", "shell", "shellSync"]
) and
cmdArg = 0
)
}

private predicate execApi(string mod, int cmdArg, int optionsArg, boolean shell) {
Expand Down Expand Up @@ -61,17 +57,16 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I
SystemCommandExecutors() {
exists(string mod |
exists(string fn |
execApi(mod, fn, cmdArg, optionsArg, shell) and
sync = getSync(fn) and
this = API::moduleImport(mod).getMember(fn).getReturn().getAUse()
execApi(mod, fn, cmdArg, optionsArg, shell, sync) and
this = API::moduleImport(mod).getMember(fn).getAnInvocation()
)
or
execApi(mod, cmdArg, optionsArg, shell) and
sync = false and
this = API::moduleImport(mod).getReturn().getAUse()
this = API::moduleImport(mod).getAnInvocation()
)
or
this = API::moduleImport("foreground-child").getReturn().getAUse() and
this = API::moduleImport("foreground-child").getACall() and
cmdArg = 0 and
optionsArg = 1 and
shell = false and
Expand Down Expand Up @@ -115,19 +110,19 @@ private class RemoteCommandExecutor extends SystemCommandExecution, DataFlow::In
int cmdArg;

RemoteCommandExecutor() {
this = API::moduleImport("remote-exec").getReturn().getAUse() and
this = API::moduleImport("remote-exec").getACall() and
cmdArg = 1
or
exists(API::Node ssh2, API::Node client |
ssh2 = API::moduleImport("ssh2") and
client in [ssh2, ssh2.getMember("Client")] and
this = client.getInstance().getMember("exec").getReturn().getAUse() and
this = client.getInstance().getMember("exec").getACall() and
cmdArg = 0
)
or
exists(API::Node ssh2stream |
ssh2stream = API::moduleImport("ssh2-streams").getMember("SSH2Stream") and
this = ssh2stream.getInstance().getMember("exec").getReturn().getAUse() and
this = ssh2stream.getInstance().getMember("exec").getACall() and
cmdArg = 1
)
}
Expand All @@ -142,7 +137,7 @@ private class RemoteCommandExecutor extends SystemCommandExecution, DataFlow::In
}

private class Opener extends SystemCommandExecution, DataFlow::InvokeNode {
Opener() { this = API::moduleImport("opener").getReturn().getAUse() }
Opener() { this = API::moduleImport("opener").getACall() }

override DataFlow::Node getACommandArgument() { result = getOptionArgument(1, "command") }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@
| tst.js:1:1:1:6 | window |
| tst.js:3:1:3:6 | window |
| tst.js:4:1:4:6 | window |
| tst.js:4:1:4:13 | window.window |
| tst.js:5:1:5:4 | self |
| tst.js:6:1:6:10 | globalThis |
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
| String | tst2.js:9:1:9:11 | this.String |
| document | tst2.js:2:1:2:26 | require ... ument") |
| document | tst.js:3:1:3:15 | window.document |
| document | tst.js:4:1:4:22 | window. ... ocument |
| document | tst.js:5:1:5:13 | self.document |
| document | tst.js:6:1:6:19 | globalThis.document |
| foo | tst3.js:4:1:4:5 | w.foo |
Expand Down