Skip to content

Commit 1daeea5

Browse files
authored
Merge pull request github#6472 from erik-krogh/apiPromise
Approved by asgerf
2 parents 170a069 + c664d7c commit 1daeea5

File tree

4 files changed

+64
-21
lines changed

4 files changed

+64
-21
lines changed

javascript/ql/src/semmle/javascript/ApiGraphs.qll

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ module API {
5757
*/
5858
CallNode getACall() { result = getReturn().getAnImmediateUse() }
5959

60+
/**
61+
* Gets a call to the function represented by this API component,
62+
* or a promisified version of the function.
63+
*/
64+
CallNode getMaybePromisifiedCall() {
65+
result = getACall()
66+
or
67+
result = Impl::getAPromisifiedInvocation(this, _, _)
68+
}
69+
6070
/**
6171
* Gets a `new` call to the function represented by this API component.
6272
*/
@@ -573,10 +583,10 @@ module API {
573583
ref = pred.getAnInvocation()
574584
or
575585
lbl = Label::promised() and
576-
PromiseFlow::loadStep(pred, ref, Promises::valueProp())
586+
PromiseFlow::loadStep(pred.getALocalUse(), ref, Promises::valueProp())
577587
or
578588
lbl = Label::promisedError() and
579-
PromiseFlow::loadStep(pred, ref, Promises::errorProp())
589+
PromiseFlow::loadStep(pred.getALocalUse(), ref, Promises::errorProp())
580590
)
581591
or
582592
exists(DataFlow::Node def, DataFlow::FunctionNode fn |
@@ -855,10 +865,9 @@ module API {
855865
succ = MkAsyncFuncResult(f)
856866
)
857867
or
858-
exists(DataFlow::SourceNode src, int bound, DataFlow::InvokeNode call |
859-
use(pred, src) and
868+
exists(int bound, DataFlow::InvokeNode call |
860869
lbl = Label::parameter(bound + call.getNumArgument()) and
861-
succ = MkSyntheticCallbackArg(src, bound, call)
870+
call = getAPromisifiedInvocation(pred, bound, succ)
862871
)
863872
}
864873

@@ -870,6 +879,18 @@ module API {
870879
/** Gets the shortest distance from the root to `nd` in the API graph. */
871880
cached
872881
int distanceFromRoot(TApiNode nd) = shortestDistances(MkRoot/0, edge/2)(_, nd, result)
882+
883+
/**
884+
* Gets a call to a promisified function represented by `callee` where
885+
* `bound` arguments have been bound.
886+
*/
887+
cached
888+
DataFlow::InvokeNode getAPromisifiedInvocation(TApiNode callee, int bound, TApiNode succ) {
889+
exists(DataFlow::SourceNode src |
890+
Impl::use(callee, src) and
891+
succ = Impl::MkSyntheticCallbackArg(src, bound, result)
892+
)
893+
}
873894
}
874895

875896
import Label as EdgeLabel
@@ -888,7 +909,8 @@ module API {
888909

889910
InvokeNode() {
890911
this = callee.getReturn().getAnImmediateUse() or
891-
this = callee.getInstance().getAnImmediateUse()
912+
this = callee.getInstance().getAnImmediateUse() or
913+
this = Impl::getAPromisifiedInvocation(callee, _, _)
892914
}
893915

894916
/** Gets the API node for the `i`th parameter of this invocation. */

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

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ module NodeJSLib {
398398
override CallExpr astNode;
399399

400400
ProcessTermination() {
401-
this = DataFlow::moduleImport("exit").getAnInvocation()
401+
this = API::moduleImport("exit").getAnInvocation()
402402
or
403403
this = processMember("exit").getACall()
404404
}
@@ -679,13 +679,14 @@ module NodeJSLib {
679679
/**
680680
* A call to a method from module `child_process`.
681681
*/
682-
private class ChildProcessMethodCall extends SystemCommandExecution, DataFlow::CallNode {
682+
private class ChildProcessMethodCall extends SystemCommandExecution, API::CallNode {
683683
string methodName;
684684

685685
ChildProcessMethodCall() {
686-
this = maybePromisified(DataFlow::moduleMember("child_process", methodName)).getACall()
687-
or
688-
this = DataFlow::moduleMember("mz/child_process", methodName).getACall()
686+
this =
687+
API::moduleImport(["mz/child_process", "child_process"])
688+
.getMember(methodName)
689+
.getMaybePromisifiedCall()
689690
}
690691

691692
private DataFlow::Node getACommandArgument(boolean shell) {
@@ -707,7 +708,7 @@ module NodeJSLib {
707708
)
708709
) and
709710
// all of the above methods take the command as their first argument
710-
result = getArgument(0)
711+
result = getParameter(0).getARhs()
711712
}
712713

713714
override DataFlow::Node getACommandArgument() { result = getACommandArgument(_) }
@@ -723,15 +724,15 @@ module NodeJSLib {
723724
methodName = "spawnSync"
724725
) and
725726
// all of the above methods take the argument list as their second argument
726-
result = getArgument(1)
727+
result = getParameter(1).getARhs()
727728
}
728729

729730
override predicate isSync() { "Sync" = methodName.suffix(methodName.length() - 4) }
730731

731732
override DataFlow::Node getOptionsArg() {
732733
not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback
733734
not result.getALocalSource() instanceof DataFlow::ArrayCreationNode and // looks like argumentlist
734-
not result = getArgument(0) and
735+
not result = getParameter(0).getARhs() and
735736
// fork/spawn and all sync methos always has options as the last argument
736737
if
737738
methodName.regexpMatch("fork.*") or
@@ -740,7 +741,7 @@ module NodeJSLib {
740741
then result = getLastArgument()
741742
else
742743
// the rest (exec/execFile) has the options argument as their second last.
743-
result = getArgument(this.getNumArgument() - 2)
744+
result = getParameter(this.getNumArgument() - 2).getARhs()
744745
}
745746
}
746747

@@ -1027,9 +1028,9 @@ module NodeJSLib {
10271028
/**
10281029
* Gets an import of the NodeJS EventEmitter.
10291030
*/
1030-
private DataFlow::SourceNode getAnEventEmitterImport() {
1031-
result = DataFlow::moduleImport("events") or
1032-
result = DataFlow::moduleMember("events", "EventEmitter")
1031+
private API::Node getAnEventEmitterImport() {
1032+
result = API::moduleImport("events") or
1033+
result = API::moduleImport("events").getMember("EventEmitter")
10331034
}
10341035

10351036
/**
@@ -1051,7 +1052,7 @@ module NodeJSLib {
10511052
*/
10521053
private class EventEmitterSubClass extends DataFlow::ClassNode {
10531054
EventEmitterSubClass() {
1054-
this.getASuperClassNode().getALocalSource() = getAnEventEmitterImport() or
1055+
this.getASuperClassNode() = getAnEventEmitterImport().getAUse() or
10551056
this.getADirectSuperClass() instanceof EventEmitterSubClass
10561057
}
10571058
}
@@ -1186,7 +1187,7 @@ module NodeJSLib {
11861187
* An instantiation of the `respjs` library, which is an EventEmitter.
11871188
*/
11881189
private class RespJS extends NodeJSEventEmitter {
1189-
RespJS() { this = DataFlow::moduleImport("respjs").getAnInstantiation() }
1190+
RespJS() { this = API::moduleImport("respjs").getAnInstantiation() }
11901191
}
11911192

11921193
/**

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,10 @@ nodes
246246
| lib/lib.js:482:40:482:43 | name |
247247
| lib/lib.js:483:30:483:33 | name |
248248
| lib/lib.js:483:30:483:33 | name |
249+
| lib/lib.js:498:45:498:48 | name |
250+
| lib/lib.js:498:45:498:48 | name |
251+
| lib/lib.js:499:31:499:34 | name |
252+
| lib/lib.js:499:31:499:34 | name |
249253
| lib/subLib2/compiled-file.ts:3:26:3:29 | name |
250254
| lib/subLib2/compiled-file.ts:3:26:3:29 | name |
251255
| lib/subLib2/compiled-file.ts:4:25:4:28 | name |
@@ -558,6 +562,10 @@ edges
558562
| lib/lib.js:482:40:482:43 | name | lib/lib.js:483:30:483:33 | name |
559563
| lib/lib.js:482:40:482:43 | name | lib/lib.js:483:30:483:33 | name |
560564
| lib/lib.js:482:40:482:43 | name | lib/lib.js:483:30:483:33 | name |
565+
| lib/lib.js:498:45:498:48 | name | lib/lib.js:499:31:499:34 | name |
566+
| lib/lib.js:498:45:498:48 | name | lib/lib.js:499:31:499:34 | name |
567+
| lib/lib.js:498:45:498:48 | name | lib/lib.js:499:31:499:34 | name |
568+
| lib/lib.js:498:45:498:48 | name | lib/lib.js:499:31:499:34 | name |
561569
| lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name |
562570
| lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name |
563571
| lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name |
@@ -648,6 +656,7 @@ edges
648656
| lib/lib.js:447:13:447:28 | "rm -rf " + name | lib/lib.js:446:20:446:23 | name | lib/lib.js:447:25:447:28 | name | $@ based on $@ is later used in $@. | lib/lib.js:447:13:447:28 | "rm -rf " + name | String concatenation | lib/lib.js:446:20:446:23 | name | library input | lib/lib.js:447:3:447:29 | asyncEx ... + name) | shell command |
649657
| lib/lib.js:478:27:478:46 | config.installedPath | lib/lib.js:477:33:477:38 | config | lib/lib.js:478:27:478:46 | config.installedPath | $@ based on $@ is later used in $@. | lib/lib.js:478:27:478:46 | config.installedPath | Path concatenation | lib/lib.js:477:33:477:38 | config | library input | lib/lib.js:479:12:479:20 | exec(cmd) | shell command |
650658
| lib/lib.js:483:13:483:33 | ' my na ... + name | lib/lib.js:482:40:482:43 | name | lib/lib.js:483:30:483:33 | name | $@ based on $@ is later used in $@. | lib/lib.js:483:13:483:33 | ' my na ... + name | String concatenation | lib/lib.js:482:40:482:43 | name | library input | lib/lib.js:485:2:485:20 | cp.exec(cmd + args) | shell command |
659+
| lib/lib.js:499:19:499:34 | "rm -rf " + name | lib/lib.js:498:45:498:48 | name | lib/lib.js:499:31:499:34 | name | $@ based on $@ is later used in $@. | lib/lib.js:499:19:499:34 | "rm -rf " + name | String concatenation | lib/lib.js:498:45:498:48 | name | library input | lib/lib.js:499:3:499:35 | MyThing ... + name) | shell command |
651660
| lib/subLib2/compiled-file.ts:4:13:4:28 | "rm -rf " + name | lib/subLib2/compiled-file.ts:3:26:3:29 | name | lib/subLib2/compiled-file.ts:4:25:4:28 | name | $@ based on $@ is later used in $@. | lib/subLib2/compiled-file.ts:4:13:4:28 | "rm -rf " + name | String concatenation | lib/subLib2/compiled-file.ts:3:26:3:29 | name | library input | lib/subLib2/compiled-file.ts:4:5:4:29 | cp.exec ... + name) | shell command |
652661
| lib/subLib2/special-file.js:4:10:4:25 | "rm -rf " + name | lib/subLib2/special-file.js:3:28:3:31 | name | lib/subLib2/special-file.js:4:22:4:25 | name | $@ based on $@ is later used in $@. | lib/subLib2/special-file.js:4:10:4:25 | "rm -rf " + name | String concatenation | lib/subLib2/special-file.js:3:28:3:31 | name | library input | lib/subLib2/special-file.js:4:2:4:26 | cp.exec ... + name) | shell command |
653662
| lib/subLib3/my-file.ts:4:10:4:25 | "rm -rf " + name | lib/subLib3/my-file.ts:3:28:3:31 | name | lib/subLib3/my-file.ts:4:22:4:25 | name | $@ based on $@ is later used in $@. | lib/subLib3/my-file.ts:4:10:4:25 | "rm -rf " + name | String concatenation | lib/subLib3/my-file.ts:3:28:3:31 | name | library input | lib/subLib3/my-file.ts:4:2:4:26 | cp.exec ... + name) | shell command |

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,4 +488,15 @@ module.exports.splitConcat = function (name) {
488488
module.exports.myCommand = function (myCommand) {
489489
let cmd = `cd ${cwd} ; ${myCommand}`; // OK - the parameter name suggests that it is purposely a shell command.
490490
cp.exec(cmd);
491-
}
491+
}
492+
493+
(function () {
494+
var MyThing = {
495+
cp: require('child_process')
496+
};
497+
498+
module.exports.myIndirectThing = function (name) {
499+
MyThing.cp.exec("rm -rf " + name); // NOT OK
500+
}
501+
});
502+

0 commit comments

Comments
 (0)