forked from github/codeql
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAsyncPackage.qll
More file actions
236 lines (207 loc) · 7.63 KB
/
AsyncPackage.qll
File metadata and controls
236 lines (207 loc) · 7.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/**
* Provides classes for working with [async](https://www.npmjs.com/package/async).
*/
import javascript
module AsyncPackage {
/**
* Gets a reference the given member of the `async` or `async-es` package.
*/
pragma[noopt]
DataFlow::SourceNode member(string name) {
result = DataFlow::moduleMember("async", name) or
result = DataFlow::moduleMember("async-es", name)
}
/**
* Gets a reference to the given member or one of its `Limit` or `Series` variants.
*
* For example, `memberVariant("map")` finds references to `map`, `mapLimit`, and `mapSeries`.
*/
DataFlow::SourceNode memberVariant(string name) {
result = member(name) or
result = member(name + "Limit") or
result = member(name + "Series")
}
/**
* Gets `Limit` or `Series` name variants for a given member name.
*
* For example, `memberNameVariant("map")` returns `map`, `mapLimit`, and `mapSeries`.
*/
bindingset[name]
private string memberNameVariant(string name) {
result = name or
result = name + "Limit" or
result = name + "Series"
}
/**
* A call to `async.waterfall`.
*/
class Waterfall extends DataFlow::InvokeNode {
Waterfall() {
this = member("waterfall").getACall() or
this = DataFlow::moduleImport("a-sync-waterfall").getACall()
}
/**
* Gets the array of tasks, if it can be found.
*/
DataFlow::ArrayCreationNode getTaskArray() { result.flowsTo(this.getArgument(0)) }
/**
* Gets the callback to invoke after the last task in the array completes.
*/
DataFlow::FunctionNode getFinalCallback() { result.flowsTo(this.getArgument(1)) }
/**
* Gets the `n`th task, if it can be found.
*/
DataFlow::FunctionNode getTask(int n) { result.flowsTo(this.getTaskArray().getElement(n)) }
/**
* Gets the number of tasks.
*/
int getNumTasks() { result = strictcount(this.getTaskArray().getAnElement()) }
}
/**
* Gets the last parameter declared by the given function, unless that's a rest parameter.
*/
private DataFlow::ParameterNode getLastParameter(DataFlow::FunctionNode function) {
not function.hasRestParameter() and
result = function.getParameter(function.getNumParameter() - 1)
}
/**
* An invocation of the callback in a waterfall task, passing arguments to the next waterfall task.
*
* Such a callback has the form `callback(err, result1, result2, ...)`. The error is propagated
* to the first parameter of the final callback, while `result1, result2, ...` are propagated to
* the parameters of the following task.
*/
private class WaterfallNextTaskCall extends DataFlow::PartialInvokeNode::Range, DataFlow::CallNode
{
Waterfall waterfall;
int n;
WaterfallNextTaskCall() { this = getLastParameter(waterfall.getTask(n)).getACall() }
override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) {
// Pass results to next task
index >= 0 and
argument = this.getArgument(index + 1) and
callback = waterfall.getTask(n + 1)
or
// For the last task, pass results to the final callback
index >= 1 and
n = waterfall.getNumTasks() - 1 and
argument = this.getArgument(index) and
callback = waterfall.getFinalCallback()
or
// Always pass error to the final callback
index = 0 and
argument = this.getArgument(0) and
callback = waterfall.getFinalCallback()
}
}
/**
* A call that iterates over a collection with asynchronous operations.
*/
class IterationCall extends DataFlow::InvokeNode {
string name;
int iteratorCallbackIndex;
int finalCallbackIndex;
IterationCall() {
(
(
name =
memberNameVariant([
"concat", "detect", "each", "eachOf", "forEach", "forEachOf", "every", "filter",
"groupBy", "map", "mapValues", "reject", "some", "sortBy",
]) and
if name.matches("%Limit")
then (
iteratorCallbackIndex = 2 and finalCallbackIndex = 3
) else (
iteratorCallbackIndex = 1 and finalCallbackIndex = 2
)
)
or
name = ["reduce", "reduceRight", "transform"] and
iteratorCallbackIndex = 2 and
finalCallbackIndex = 3
) and
this = member(name).getACall()
}
/**
* Gets the name of the iteration call
*/
string getName() { result = name }
/**
* Gets the iterator callback index
*/
int getIteratorCallbackIndex() { result = iteratorCallbackIndex }
/**
* Gets the final callback index
*/
int getFinalCallbackIndex() { result = finalCallbackIndex }
/**
* Gets the node holding the collection being iterated over.
*/
DataFlow::Node getCollection() { result = this.getArgument(0) }
/**
* Gets the node holding the function being called for each element in the collection.
*/
DataFlow::FunctionNode getIteratorCallback() {
result = this.getCallback(iteratorCallbackIndex)
}
/**
* Gets the node holding the function being invoked after iteration is complete. (may not exist)
*/
DataFlow::FunctionNode getFinalCallback() { result = this.getCallback(finalCallbackIndex) }
}
private class IterationCallFlowSummary extends DataFlow::SummarizedCallable {
private int callbackArgIndex;
IterationCallFlowSummary() {
this = "async.IteratorCall(callbackArgIndex=" + callbackArgIndex + ")" and
callbackArgIndex in [1 .. 3]
}
override DataFlow::InvokeNode getACallSimple() {
result instanceof IterationCall and
result.(IterationCall).getIteratorCallbackIndex() = callbackArgIndex
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = true and
input = "Argument[0]." + ["ArrayElement", "SetElement", "IteratorElement", "AnyMember"] and
output = "Argument[" + callbackArgIndex + "].Parameter[0]"
}
}
/**
* A taint step from the return value of an iterator callback to the result of the iteration
* call.
*
* For example: `item + taint() -> result` in `async.map(data, (item, cb) => cb(null, item + taint()), (err, result) => {})`.
*/
private class IterationOutputTaintStep extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
exists(
DataFlow::FunctionNode iteratee, DataFlow::FunctionNode final, int i, IterationCall call
|
iteratee = call.getIteratorCallback() and
final = call.getFinalCallback() and // Require a closure to avoid spurious call/return mismatch.
pred = getLastParameter(iteratee).getACall().getArgument(i) and
succ = final.getParameter(i) and
exists(string name | name = call.getName() |
name = ["concat", "map", "reduce", "reduceRight"]
)
)
}
}
/**
* A taint step from the input of an iteration call, directly to its output.
*
* For example: `data -> result` in `async.sortBy(data, orderingFn, (err, result) => {})`.
*/
private class IterationPreserveTaintStepFlowSummary extends DataFlow::SummarizedCallable {
IterationPreserveTaintStepFlowSummary() { this = "async.sortBy" }
override DataFlow::InvokeNode getACallSimple() {
result instanceof IterationCall and
result.(IterationCall).getName() = "sortBy"
}
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
preservesValue = false and
input = "Argument[0]." + ["ArrayElement", "SetElement", "IteratorElement", "AnyMember"] and
output = "Argument[2].Parameter[1]"
}
}
}