refactor the getOptionsArg predicate into the SystemCommandExecution class

This commit is contained in:
Erik Krogh Kristensen
2020-02-24 12:59:20 +01:00
parent 75c1852ee4
commit 473787a426
7 changed files with 65 additions and 25 deletions

View File

@@ -25,6 +25,11 @@ abstract class SystemCommandExecution extends DataFlow::Node {
/** Holds if the command execution happens synchronously. */
abstract predicate isSync();
/**
* Gets the data-flow node (if it exists) for an options argument.
*/
abstract DataFlow::Node getOptionsArg();
}
/**

View File

@@ -627,6 +627,18 @@ module NodeJSLib {
override predicate isSync() {
"Sync" = methodName.suffix(methodName.length() - 4)
}
override DataFlow::Node getOptionsArg() {
not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback
not result.getALocalSource() instanceof DataFlow::ArrayCreationNode and // looks like argumentlist
not result = getArgument(0) and
// fork/spawn and all sync methos always has options as the last argument
if methodName.regexpMatch("fork.*") or methodName.regexpMatch("spawn.*") or methodName.regexpMatch(".*Sync") then
result = getLastArgument()
else
// the rest (exec/execFile) has the options argument as their second last.
result = getArgument(this.getNumArgument() - 2)
}
}
/**

View File

@@ -162,6 +162,13 @@ module ShellJS {
override predicate isShellInterpreted(DataFlow::Node arg) { arg = getACommandArgument() }
override predicate isSync() {none ()}
override DataFlow::Node getOptionsArg() {
result = getLastArgument() and
not result = getArgument(0) and
not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback
not result.getALocalSource() instanceof DataFlow::ArrayCreationNode // looks like argumentlist
}
}
/**

View File

@@ -7,6 +7,7 @@ import javascript
private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::InvokeNode {
int cmdArg;
int optionsArg;
boolean shell;
boolean sync;
@@ -14,9 +15,9 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I
SystemCommandExecutors() {
exists(string mod, DataFlow::SourceNode callee |
exists(string method |
mod = "cross-spawn" and method = "sync" and cmdArg = 0 and shell = false
mod = "cross-spawn" and method = "sync" and cmdArg = 0 and shell = false and optionsArg = -1
or
mod = "execa" and
mod = "execa" and optionsArg = -1 and
(
shell = false and
(
@@ -40,21 +41,21 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I
(
shell = false and
(
mod = "cross-spawn" and cmdArg = 0
mod = "cross-spawn" and cmdArg = 0 and optionsArg = -1
or
mod = "cross-spawn-async" and cmdArg = 0
mod = "cross-spawn-async" and cmdArg = 0 and optionsArg = -1
or
mod = "exec-async" and cmdArg = 0
mod = "exec-async" and cmdArg = 0 and optionsArg = -1
or
mod = "execa" and cmdArg = 0
mod = "execa" and cmdArg = 0 and optionsArg = -1
)
or
shell = true and
(
mod = "exec" and
mod = "exec" and optionsArg = -2 and
cmdArg = 0
or
mod = "remote-exec" and cmdArg = 1
mod = "remote-exec" and cmdArg = 1 and optionsArg = -1
)
) and
callee = DataFlow::moduleImport(mod)
@@ -69,8 +70,16 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I
arg = getACommandArgument() and shell = true
}
override predicate isSync() {
sync = true
override predicate isSync() { sync = true }
override DataFlow::Node getOptionsArg() {
(if optionsArg < 0 then
result = getArgument(getNumArgument() - optionsArg)
else
result = getArgument(optionsArg)) and
not result = getArgument(0) and
not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback
not result.getALocalSource() instanceof DataFlow::ArrayCreationNode // looks like argumentlist
}
}

View File

@@ -41,21 +41,7 @@ private class CommandCall extends DataFlow::InvokeNode {
* Gets the data-flow node (if it exists) for an options argument for an `exec`-like call.
*/
DataFlow::Node getOptionsArg() {
exists(int n |
n >= 1 and
// if there is a command-list, then the options is at least the third argument.
(not exists(command.getArgumentList()) or n >= 2) and
// async exec calls can have a callback as their last call.
if command.isSync() or not exists(getCallback())
then n < getNumArgument()
else n < getNumArgument() - 1
|
result = getArgument(n)
)
or
// Fallback in case normal API conventions are broken.
result = getAnArgument() and
result.getALocalSource() instanceof DataFlow::ObjectLiteralNode
result = command.getOptionsArg()
}
/**

View File

@@ -71,6 +71,23 @@ syncCommand
| uselesscat.js:100:1:100:56 | execFil ... ptions) |
| uselesscat.js:104:1:104:31 | execFil ... cat` ]) |
| uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) |
options
| child_process-test.js:53:5:53:59 | cp.spaw ... cmd])) | child_process-test.js:53:25:53:58 | ['/C', ... , cmd]) |
| child_process-test.js:54:5:54:50 | cp.spaw ... t(cmd)) | child_process-test.js:54:25:54:49 | ['/C', ... at(cmd) |
| child_process-test.js:64:3:64:21 | cp.spawn(cmd, args) | child_process-test.js:64:17:64:20 | args |
| uselesscat.js:28:1:28:39 | execSyn ... 1000}) | uselesscat.js:28:28:28:38 | {uid: 1000} |
| uselesscat.js:30:1:30:64 | exec('c ... t) { }) | uselesscat.js:30:26:30:38 | { cwd: './' } |
| uselesscat.js:34:1:34:54 | execSyn ... utf8'}) | uselesscat.js:34:36:34:53 | {encoding: 'utf8'} |
| uselesscat.js:36:1:36:77 | execSyn ... utf8'}) | uselesscat.js:36:36:36:76 | { uid: ... 'utf8'} |
| uselesscat.js:69:1:72:2 | execFil ... ut);\\n}) | uselesscat.js:69:38:69:55 | {encoding: 'utf8'} |
| uselesscat.js:74:1:74:60 | execFil ... utf8'}) | uselesscat.js:74:42:74:59 | {encoding: 'utf8'} |
| uselesscat.js:79:1:79:46 | execFil ... opts) | uselesscat.js:79:42:79:45 | opts |
| uselesscat.js:82:1:82:90 | execFil ... String) | uselesscat.js:82:42:82:89 | anOptsF ... oString |
| uselesscat.js:84:1:84:115 | execFil ... ring'}) | uselesscat.js:84:42:84:114 | {encodi ... tring'} |
| uselesscat.js:86:1:86:75 | execFil ... utf8'}) | uselesscat.js:86:57:86:74 | {encoding: 'utf8'} |
| uselesscat.js:100:1:100:56 | execFil ... ptions) | uselesscat.js:100:42:100:55 | unknownOptions |
| uselesscat.js:111:1:111:51 | spawn(' ... it'] }) | uselesscat.js:111:14:111:50 | { stdio ... rit'] } |
| uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | uselesscat.js:136:51:138:1 | { // NO ... utf8'\\n} |
#select
| False negative | uselesscat.js:54:42:54:69 | // NOT ... lagged] |
| False positive | uselesscat.js:44:37:44:85 | // OK [ ... le read |

View File

@@ -21,4 +21,8 @@ query string readFile(UselessCat cat) { result = PrettyPrintCatCall::createReadF
query SystemCommandExecution syncCommand() {
result.isSync()
}
query DataFlow::Node options(SystemCommandExecution sys) {
result = sys.getOptionsArg()
}