Skip to content

Commit 44c5d36

Browse files
committed
JS: Simple RxJS model
1 parent 00cd064 commit 44c5d36

File tree

2 files changed

+75
-0
lines changed

2 files changed

+75
-0
lines changed

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ import semmle.javascript.frameworks.PropertyProjection
9898
import semmle.javascript.frameworks.React
9999
import semmle.javascript.frameworks.ReactNative
100100
import semmle.javascript.frameworks.Request
101+
import semmle.javascript.frameworks.RxJS
101102
import semmle.javascript.frameworks.ServerLess
102103
import semmle.javascript.frameworks.ShellJS
103104
import semmle.javascript.frameworks.SystemCommandExecutors
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Provides taint steps modeling flow through `rxjs` Observable objects.
3+
*/
4+
private import javascript
5+
6+
/**
7+
* A step `x -> y` in `x.subscribe(y => ...)`, modeling flow out of an rxjs Observable.
8+
*/
9+
private class RxJsSubscribeStep extends TaintTracking::AdditionalTaintStep, DataFlow::MethodCallNode {
10+
RxJsSubscribeStep() {
11+
getMethodName() = "subscribe"
12+
}
13+
14+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
15+
pred = getReceiver() and
16+
succ = getCallback(0).getParameter(0)
17+
}
18+
}
19+
20+
/**
21+
* Holds if a tainted value sent into the given `pipe` should propagate to `arg`.
22+
*/
23+
private DataFlow::Node pipeInput(DataFlow::CallNode pipe) {
24+
pipe = DataFlow::moduleMember("rxjs/operators", ["map", "filter"]).getACall() and
25+
result = pipe.getCallback(0).getParameter(0)
26+
}
27+
28+
/**
29+
* Holds if a tainted value in `output` should propagate to the output of the given pipe.
30+
*/
31+
private DataFlow::Node pipeOutput(DataFlow::CallNode pipe) {
32+
pipe = DataFlow::moduleMember("rxjs/operators", "map").getACall() and
33+
result = pipe.getCallback(0).getReturnNode()
34+
or
35+
pipe = DataFlow::moduleMember("rxjs/operators", "filter").getACall() and
36+
result = pipe.getCallback(0).getParameter(0)
37+
}
38+
39+
/**
40+
* Holds if `pipe` acts as the identity function for success values.
41+
*
42+
* We currently lack a data-flow node to represent its input/ouput so it must
43+
* be special-cased.
44+
*/
45+
private predicate isIdentityPipe(DataFlow::CallNode pipe) {
46+
pipe = DataFlow::moduleMember("rxjs/operators", "catchError").getACall()
47+
}
48+
49+
/**
50+
* A step in or out of the map callback in a call of form `x.pipe(map(y => ...))`.
51+
*/
52+
private class RxJsPipeMapStep extends TaintTracking::AdditionalTaintStep, DataFlow::MethodCallNode {
53+
RxJsPipeMapStep() {
54+
getMethodName() = "pipe"
55+
}
56+
57+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
58+
pred = getReceiver() and
59+
succ = pipeInput(getArgument(0).getALocalSource())
60+
or
61+
exists(int i |
62+
pred = pipeOutput(getArgument(i).getALocalSource()) and
63+
succ = pipeInput(getArgument(i + 1).getALocalSource())
64+
)
65+
or
66+
pred = pipeOutput(getLastArgument().getALocalSource()) and
67+
succ = this
68+
or
69+
// Handle a common case where the last step is `catchError`.
70+
isIdentityPipe(getLastArgument().getALocalSource()) and
71+
pred = pipeOutput(getArgument(getNumArgument() - 2)) and
72+
succ = this
73+
}
74+
}

0 commit comments

Comments
 (0)