Skip to content

Commit 87d283a

Browse files
committed
add tests for third party command execution libraries (and two small fixes)
1 parent d540cae commit 87d283a

File tree

4 files changed

+50
-6
lines changed

4 files changed

+50
-6
lines changed

javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,15 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I
7070
arg = getACommandArgument() and shell = true
7171
}
7272

73+
override DataFlow::Node getArgumentList() { shell = false and result = getArgument(1) }
74+
7375
override predicate isSync() { sync = true }
7476

7577
override DataFlow::Node getOptionsArg() {
7678
(if optionsArg < 0 then
77-
result = getArgument(getNumArgument() - optionsArg)
79+
result = getArgument(getNumArgument() + optionsArg) and getNumArgument() + optionsArg > cmdArg
7880
else
7981
result = getArgument(optionsArg)) and
80-
not result = getArgument(0) and
8182
not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback
8283
not result.getALocalSource() instanceof DataFlow::ArrayCreationNode // looks like argumentlist
8384
}

javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ private class CommandCall extends DataFlow::InvokeNode {
2121
predicate isSync() { command.isSync() }
2222

2323
/**
24-
* Gets an argument to this command execution that specifies the argument list to the command.
24+
* Gets a list that specifies the arguments given to the command.
2525
*/
26-
DataFlow::Node getArgumentList() { result = command.getArgumentList() }
26+
DataFlow::ArrayCreationNode getArgumentList() { result = command.getArgumentList().getALocalSource() }
2727

2828
/**
2929
* Gets the callback (if it exists) for an async `exec`-like call.
@@ -35,7 +35,7 @@ private class CommandCall extends DataFlow::InvokeNode {
3535
/**
3636
* Holds if the executed command execution has an argument list as a separate argument.
3737
*/
38-
predicate hasArgumentList() { exists(command.getArgumentList()) }
38+
predicate hasArgumentList() { exists(getArgumentList()) }
3939

4040
/**
4141
* Gets the data-flow node (if it exists) for an options argument for an `exec`-like call.

javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ readFile
2222
| uselesscat.js:121:12:121:64 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) |
2323
| uselesscat.js:127:14:127:66 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) |
2424
| uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | fs.readFileSync("/etc/dnsmasq.conf", ...) |
25+
| uselesscat.js:146:1:146:61 | shelljs ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) |
26+
| uselesscat.js:147:1:147:47 | shelljs ... utf8'}) | fs.readFile("foo/bar", {encoding: 'utf8'}) |
27+
| uselesscat.js:148:1:148:81 | shelljs ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) |
28+
| uselesscat.js:151:1:151:48 | cspawn( ... tf8' }) | fs.readFile("foo/bar", { encoding: 'utf8' }) |
29+
| uselesscat.js:152:1:152:82 | cspawn( ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) |
30+
| uselesscat.js:153:1:153:60 | cspawn( ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) |
31+
| uselesscat.js:154:1:154:26 | cspawn( ... /bar']) | fs.readFile("foo/bar") |
32+
| uselesscat.js:158:16:158:46 | cspawn. ... /bar']) | fs.readFileSync("foo/bar") |
33+
| uselesscat.js:159:16:159:68 | cspawn. ... tf8' }) | fs.readFileSync("foo/bar", { encoding: 'utf8' }) |
34+
| uselesscat.js:162:1:162:56 | execmod ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) |
35+
| uselesscat.js:163:1:163:42 | execmod ... utf8'}) | fs.readFile("foo/bar") |
36+
| uselesscat.js:164:1:164:76 | execmod ... (out)}) | fs.readFile("foo/bar", {encoding: 'utf8'}, (err, out) => {...}) |
2537
syncCommand
2638
| child_process-test.js:9:5:9:22 | cp.execSync("foo") |
2739
| child_process-test.js:11:5:11:26 | cp.exec ... ("foo") |
@@ -71,6 +83,8 @@ syncCommand
7183
| uselesscat.js:100:1:100:56 | execFil ... ptions) |
7284
| uselesscat.js:104:1:104:31 | execFil ... cat` ]) |
7385
| uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) |
86+
| uselesscat.js:158:16:158:46 | cspawn. ... /bar']) |
87+
| uselesscat.js:159:16:159:68 | cspawn. ... tf8' }) |
7488
options
7589
| child_process-test.js:53:5:53:59 | cp.spaw ... cmd])) | child_process-test.js:53:25:53:58 | ['/C', ... , cmd]) |
7690
| child_process-test.js:54:5:54:50 | cp.spaw ... t(cmd)) | child_process-test.js:54:25:54:49 | ['/C', ... at(cmd) |
@@ -88,6 +102,11 @@ options
88102
| uselesscat.js:100:1:100:56 | execFil ... ptions) | uselesscat.js:100:42:100:55 | unknownOptions |
89103
| uselesscat.js:111:1:111:51 | spawn(' ... it'] }) | uselesscat.js:111:14:111:50 | { stdio ... rit'] } |
90104
| uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | uselesscat.js:136:51:138:1 | { // NO ... utf8'\\n} |
105+
| uselesscat.js:147:1:147:47 | shelljs ... utf8'}) | uselesscat.js:147:29:147:46 | {encoding: 'utf8'} |
106+
| uselesscat.js:151:1:151:48 | cspawn( ... tf8' }) | uselesscat.js:151:28:151:47 | { encoding: 'utf8' } |
107+
| uselesscat.js:156:1:156:35 | cspawn( ... tf8' }) | uselesscat.js:156:15:156:34 | { encoding: 'utf8' } |
108+
| uselesscat.js:159:16:159:68 | cspawn. ... tf8' }) | uselesscat.js:159:48:159:67 | { encoding: 'utf8' } |
109+
| uselesscat.js:164:1:164:76 | execmod ... (out)}) | uselesscat.js:164:24:164:41 | {encoding: 'utf8'} |
91110
#select
92111
| False negative | uselesscat.js:54:42:54:69 | // NOT ... lagged] |
93112
| False positive | uselesscat.js:44:37:44:85 | // OK [ ... le read |

javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,28 @@ const stdout2 = execSync('cat /etc/dnsmasq.conf', { // NOT OK.
139139

140140
exec('/bin/cat', function (e, s) {}); // OK
141141

142-
spawn("cat") // OK
142+
spawn("cat") // OK
143+
144+
145+
var shelljs = require("shelljs");
146+
shelljs.exec("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK
147+
shelljs.exec("cat foo/bar", {encoding: 'utf8'}); // NOT OK
148+
shelljs.exec("cat foo/bar", {encoding: 'utf8'}, (err, out) => {console.log(out)}); // NOT OK
149+
150+
let cspawn = require('cross-spawn');
151+
cspawn('cat', ['foo/bar'], { encoding: 'utf8' }); // NOT OK
152+
cspawn('cat', ['foo/bar'], { encoding: 'utf8' }, (err, out) => {console.log(out)}); // NOT OK
153+
cspawn('cat', ['foo/bar'], (err, out) => {console.log(out)}); // NOT OK
154+
cspawn('cat', ['foo/bar']); // NOT OK
155+
cspawn('cat', (err, out) => {console.log(out)}); // OK
156+
cspawn('cat', { encoding: 'utf8' }); // OK
157+
158+
let myResult = cspawn.sync('cat', ['foo/bar']); // NOT OK
159+
let myResult = cspawn.sync('cat', ['foo/bar'], { encoding: 'utf8' }); // NOT OK
160+
161+
var execmod = require('exec');
162+
execmod("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK
163+
execmod("cat foo/bar", {encoding: 'utf8'}); // NOT OK
164+
execmod("cat foo/bar", {encoding: 'utf8'}, (err, out) => {console.log(out)}); // NOT OK
165+
166+

0 commit comments

Comments
 (0)