mirror of
https://github.com/github/codeql.git
synced 2026-02-26 11:53:42 +01:00
297 lines
9.2 KiB
Plaintext
297 lines
9.2 KiB
Plaintext
/**
|
|
* Models the `execa` library in terms of `FileSystemAccess` and `SystemCommandExecution`.
|
|
*/
|
|
|
|
import javascript
|
|
|
|
/**
|
|
* Provide model for [Execa](https://github.com/sindresorhus/execa) package
|
|
*/
|
|
module Execa {
|
|
/**
|
|
* The Execa input file read and output file write
|
|
*/
|
|
class ExecaFileSystemAccess extends FileSystemReadAccess, DataFlow::Node {
|
|
API::Node execaArg;
|
|
boolean isPipedToFile;
|
|
|
|
ExecaFileSystemAccess() {
|
|
(
|
|
execaArg = API::moduleImport("execa").getMember("$").getParameter(0) and
|
|
isPipedToFile = false
|
|
or
|
|
execaArg =
|
|
API::moduleImport("execa")
|
|
.getMember(["execa", "execaCommand", "execaCommandSync", "execaSync"])
|
|
.getParameter([0, 1, 2]) and
|
|
isPipedToFile = false
|
|
or
|
|
execaArg =
|
|
API::moduleImport("execa")
|
|
.getMember(["execa", "execaCommand", "execaCommandSync", "execaSync"])
|
|
.getReturn()
|
|
.getMember(["pipeStdout", "pipeAll", "pipeStderr"])
|
|
.getParameter(0) and
|
|
isPipedToFile = true
|
|
) and
|
|
this = execaArg.asSink()
|
|
}
|
|
|
|
override DataFlow::Node getADataNode() { none() }
|
|
|
|
override DataFlow::Node getAPathArgument() {
|
|
result = execaArg.getMember("inputFile").asSink() and isPipedToFile = false
|
|
or
|
|
result = execaArg.asSink() and isPipedToFile = true
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `execa.execa` or `execa.execaSync`
|
|
*/
|
|
class ExecaCall extends API::CallNode {
|
|
boolean isSync;
|
|
|
|
ExecaCall() {
|
|
this = API::moduleImport("execa").getMember("execa").getACall() and
|
|
isSync = false
|
|
or
|
|
this = API::moduleImport("execa").getMember("execaSync").getACall() and
|
|
isSync = true
|
|
or
|
|
this = API::moduleImport("execa").getACall() and
|
|
isSync = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The system command execution nodes for `execa.execa` or `execa.execaSync` functions
|
|
*/
|
|
class ExecaExec extends SystemCommandExecution, ExecaCall {
|
|
ExecaExec() { isSync = [false, true] }
|
|
|
|
override DataFlow::Node getACommandArgument() { result = this.getArgument(0) }
|
|
|
|
override predicate isShellInterpreted(DataFlow::Node arg) {
|
|
// if shell: true then first and second args are sinks
|
|
// options can be third argument
|
|
arg = [this.getArgument(0), this.getParameter(1).getArrayElement().asSink()] and
|
|
isExecaShellEnable(this.getParameter(2))
|
|
or
|
|
// options can be second argument
|
|
arg = this.getArgument(0) and
|
|
isExecaShellEnable(this.getParameter(1))
|
|
}
|
|
|
|
override DataFlow::Node getArgumentList() {
|
|
// execa(cmd, [arg]);
|
|
exists(DataFlow::Node arg | arg = this.getArgument(1) |
|
|
// if it is a object then it is a option argument not command argument
|
|
result = arg and not arg.asExpr() instanceof ObjectExpr
|
|
)
|
|
}
|
|
|
|
override predicate isSync() { isSync = true }
|
|
|
|
override DataFlow::Node getOptionsArg() {
|
|
result = this.getLastArgument() and result.asExpr() instanceof ObjectExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `execa.$` or `execa.$.sync` or `execa.$({})` or `execa.$.sync({})` tag functions
|
|
*/
|
|
private class ExecaScriptCall extends API::CallNode {
|
|
boolean isSync;
|
|
|
|
ExecaScriptCall() {
|
|
exists(API::Node script |
|
|
script =
|
|
[
|
|
API::moduleImport("execa").getMember("$"),
|
|
API::moduleImport("execa").getMember("$").getReturn()
|
|
]
|
|
|
|
|
this = script.getACall() and
|
|
isSync = false
|
|
or
|
|
this = script.getMember("sync").getACall() and
|
|
isSync = true
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The system command execution nodes for `execa.$` or `execa.$.sync` tag functions
|
|
*/
|
|
class ExecaScript extends SystemCommandExecution, ExecaScriptCall {
|
|
ExecaScript() { isSync = [false, true] }
|
|
|
|
override DataFlow::Node getACommandArgument() {
|
|
result = this.getParameter(1).asSink() and
|
|
not isTaggedTemplateFirstChildAnElement(this.getParameter(1).asSink().asExpr().getParent())
|
|
}
|
|
|
|
override predicate isShellInterpreted(DataFlow::Node arg) {
|
|
isExecaShellEnable(this.getParameter(0)) and
|
|
arg = this.getAParameter().asSink()
|
|
}
|
|
|
|
override DataFlow::Node getArgumentList() {
|
|
result = this.getParameter(any(int i | i >= 1)).asSink() and
|
|
isTaggedTemplateFirstChildAnElement(this.getParameter(1).asSink().asExpr().getParent())
|
|
or
|
|
result = this.getParameter(any(int i | i >= 2)).asSink() and
|
|
not isTaggedTemplateFirstChildAnElement(this.getParameter(1).asSink().asExpr().getParent())
|
|
}
|
|
|
|
override DataFlow::Node getOptionsArg() { result = this.getParameter(0).asSink() }
|
|
|
|
override predicate isSync() { isSync = true }
|
|
}
|
|
|
|
/**
|
|
* A call to `execa.execaCommandSync` or `execa.execaCommand`
|
|
*/
|
|
private class ExecaCommandCall extends API::CallNode {
|
|
boolean isSync;
|
|
|
|
ExecaCommandCall() {
|
|
this = API::moduleImport("execa").getMember("execaCommandSync").getACall() and
|
|
isSync = true
|
|
or
|
|
this = API::moduleImport("execa").getMember("execaCommand").getACall() and
|
|
isSync = false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The system command execution nodes for `execa.execaCommand` or `execa.execaCommandSync` functions
|
|
*/
|
|
class ExecaCommandExec extends SystemCommandExecution, ExecaCommandCall {
|
|
ExecaCommandExec() { isSync = [false, true] }
|
|
|
|
override DataFlow::Node getACommandArgument() {
|
|
result = this.(DataFlow::CallNode).getArgument(0)
|
|
}
|
|
|
|
override DataFlow::Node getArgumentList() {
|
|
// execaCommand(`${cmd} ${arg}`);
|
|
result.asExpr() = this.getParameter(0).asSink().asExpr().getAChildExpr() and
|
|
not result.asExpr() = this.getArgument(0).asExpr().getChildExpr(0)
|
|
}
|
|
|
|
override predicate isShellInterpreted(DataFlow::Node arg) {
|
|
// execaCommandSync(`${cmd} ${arg}`, {shell: true})
|
|
arg.asExpr() = this.getArgument(0).asExpr().getAChildExpr+() and
|
|
isExecaShellEnable(this.getParameter(1))
|
|
or
|
|
// there is only one argument that is constructed in previous nodes,
|
|
// it makes sanitizing really hard to select whether it is vulnerable to argument injection or not
|
|
arg = this.getParameter(0).asSink() and
|
|
not exists(this.getArgument(0).asExpr().getChildExpr(1))
|
|
}
|
|
|
|
override predicate isSync() { isSync = true }
|
|
|
|
override DataFlow::Node getOptionsArg() {
|
|
result = this.getLastArgument() and result.asExpr() instanceof ObjectExpr
|
|
}
|
|
}
|
|
|
|
/** Gets a TemplateLiteral and check if first child is a template element */
|
|
private predicate isTaggedTemplateFirstChildAnElement(TemplateLiteral templateLit) {
|
|
exists(templateLit.getChildExpr(0).(TemplateElement))
|
|
}
|
|
|
|
/**
|
|
* Holds whether Execa has shell enabled options or not, get Parameter responsible for options
|
|
*/
|
|
pragma[inline]
|
|
private predicate isExecaShellEnable(API::Node n) {
|
|
n.getMember("shell").asSink().asExpr().(BooleanLiteral).getValue() = "true"
|
|
}
|
|
|
|
/**
|
|
* A call to `execa.node`
|
|
*/
|
|
class ExecaNodeCall extends SystemCommandExecution, API::CallNode {
|
|
ExecaNodeCall() { this = API::moduleImport("execa").getMember("node").getACall() }
|
|
|
|
override DataFlow::Node getACommandArgument() { result = this.getArgument(0) }
|
|
|
|
override predicate isShellInterpreted(DataFlow::Node arg) { none() }
|
|
|
|
override DataFlow::Node getArgumentList() {
|
|
result = this.getArgument(1) and
|
|
not result.asExpr() instanceof ObjectExpr
|
|
}
|
|
|
|
override predicate isSync() { none() }
|
|
|
|
override DataFlow::Node getOptionsArg() {
|
|
result = this.getLastArgument() and
|
|
result.asExpr() instanceof ObjectExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `execa.stdout`, `execa.stderr`, or `execa.sync`
|
|
*/
|
|
class ExecaStreamCall extends SystemCommandExecution, API::CallNode {
|
|
string methodName;
|
|
|
|
ExecaStreamCall() {
|
|
methodName in ["stdout", "stderr", "sync"] and
|
|
this = API::moduleImport("execa").getMember(methodName).getACall()
|
|
}
|
|
|
|
override DataFlow::Node getACommandArgument() { result = this.getArgument(0) }
|
|
|
|
override predicate isShellInterpreted(DataFlow::Node arg) {
|
|
arg = this.getArgument(0) and
|
|
isExecaShellEnable(this.getParameter([1, 2]))
|
|
}
|
|
|
|
override DataFlow::Node getArgumentList() {
|
|
result = this.getArgument(1) and
|
|
not result.asExpr() instanceof ObjectExpr
|
|
}
|
|
|
|
override predicate isSync() { methodName = "sync" }
|
|
|
|
override DataFlow::Node getOptionsArg() {
|
|
result = this.getLastArgument() and
|
|
result.asExpr() instanceof ObjectExpr
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A call to `execa.shell` or `execa.shellSync`
|
|
*/
|
|
class ExecaShellCall extends SystemCommandExecution, API::CallNode {
|
|
boolean sync;
|
|
|
|
ExecaShellCall() {
|
|
this = API::moduleImport("execa").getMember("shell").getACall() and
|
|
sync = false
|
|
or
|
|
this = API::moduleImport("execa").getMember("shellSync").getACall() and
|
|
sync = true
|
|
}
|
|
|
|
override DataFlow::Node getACommandArgument() { result = this.getArgument(0) }
|
|
|
|
override predicate isShellInterpreted(DataFlow::Node arg) { arg = this.getACommandArgument() }
|
|
|
|
override DataFlow::Node getArgumentList() { none() }
|
|
|
|
override predicate isSync() { sync = true }
|
|
|
|
override DataFlow::Node getOptionsArg() {
|
|
result = this.getArgument(1) and
|
|
result.asExpr() instanceof ObjectExpr
|
|
}
|
|
}
|
|
}
|