Skip to content

Commit cf5b1f0

Browse files
authored
Merge pull request github#3019 from erik-krogh/ArrayStep
Approved by asgerf
2 parents a413a32 + 91bc124 commit cf5b1f0

File tree

12 files changed

+357
-88
lines changed

12 files changed

+357
-88
lines changed

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import Customizations
66
import semmle.javascript.Aliases
77
import semmle.javascript.AMD
8+
import semmle.javascript.Arrays
89
import semmle.javascript.AST
910
import semmle.javascript.BasicBlocks
1011
import semmle.javascript.Base64
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
import javascript
2+
private import semmle.javascript.dataflow.InferredTypes
3+
4+
/**
5+
* Classes and predicates for modelling TaintTracking steps for arrays.
6+
*/
7+
module ArrayTaintTracking {
8+
/**
9+
* A taint propagating data flow edge caused by the builtin array functions.
10+
*/
11+
private class ArrayFunctionTaintStep extends TaintTracking::AdditionalTaintStep {
12+
DataFlow::CallNode call;
13+
14+
ArrayFunctionTaintStep() { this = call }
15+
16+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
17+
arrayFunctionTaintStep(pred, succ, call)
18+
}
19+
}
20+
21+
/**
22+
* A taint propagating data flow edge from `pred` to `succ` caused by a call `call` to a builtin array functions.
23+
*/
24+
predicate arrayFunctionTaintStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode call) {
25+
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
26+
// `elt` and `ary`; similar for `forEach`
27+
exists(string name, Function f, int i |
28+
(name = "map" or name = "forEach") and
29+
(i = 0 or i = 2) and
30+
call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and
31+
call.(DataFlow::MethodCallNode).getMethodName() = name and
32+
pred = call.getReceiver() and
33+
succ = DataFlow::parameterNode(f.getParameter(i))
34+
)
35+
or
36+
// `array.map` with tainted return value in callback
37+
exists(DataFlow::FunctionNode f |
38+
call.(DataFlow::MethodCallNode).getMethodName() = "map" and
39+
call.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow
40+
pred = f.getAReturn() and
41+
succ = call
42+
)
43+
or
44+
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
45+
exists(string name |
46+
name = "push" or
47+
name = "unshift"
48+
|
49+
pred = call.getAnArgument() and
50+
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
51+
)
52+
or
53+
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
54+
exists(string name |
55+
name = "push" or
56+
name = "unshift"
57+
|
58+
pred = call.getASpreadArgument() and
59+
// Make sure we handle reflective calls
60+
succ = call.getReceiver().getALocalSource() and
61+
call.getCalleeName() = name
62+
)
63+
or
64+
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
65+
exists(string name | name = "splice" |
66+
pred = call.getArgument(2) and
67+
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
68+
)
69+
or
70+
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
71+
exists(string name |
72+
name = "pop" or
73+
name = "shift" or
74+
name = "slice" or
75+
name = "splice"
76+
|
77+
call.(DataFlow::MethodCallNode).calls(pred, name) and
78+
succ = call
79+
)
80+
or
81+
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
82+
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and
83+
pred = call.getAnArgument() and
84+
succ = call
85+
or
86+
// `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
87+
call.(DataFlow::MethodCallNode).calls(pred, "concat") and
88+
succ = call
89+
or
90+
call.(DataFlow::MethodCallNode).getMethodName() = "concat" and
91+
succ = call and
92+
pred = call.getAnArgument()
93+
}
94+
}
95+
96+
/**
97+
* Classes and predicates for modelling data-flow for arrays.
98+
*/
99+
private module ArrayDataFlow {
100+
/**
101+
* Gets a pseudo-field representing an element inside an array.
102+
*/
103+
private string arrayElement() { result = "$arrayElement$" }
104+
105+
/**
106+
* A step for storing an element on an array using `arr.push(e)` or `arr.unshift(e)`.
107+
*/
108+
private class ArrayAppendStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
109+
ArrayAppendStep() {
110+
this.getMethodName() = "push" or
111+
this.getMethodName() = "unshift"
112+
}
113+
114+
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
115+
prop = arrayElement() and
116+
(element = this.getAnArgument() or element = this.getASpreadArgument()) and
117+
obj = this.getReceiver().getALocalSource()
118+
}
119+
}
120+
121+
/**
122+
* A step for reading/writing an element from an array inside a for-loop.
123+
* E.g. a read from `foo[i]` to `bar` in `for(var i = 0; i < arr.length; i++) {bar = foo[i]}`.
124+
*/
125+
private class ArrayIndexingStep extends DataFlow::AdditionalFlowStep, DataFlow::Node {
126+
DataFlow::PropRef read;
127+
128+
ArrayIndexingStep() {
129+
read = this and
130+
forex(InferredType type | type = read.getPropertyNameExpr().flow().analyze().getAType() |
131+
type = TTNumber()
132+
) and
133+
exists(VarAccess i, ExprOrVarDecl init |
134+
i = read.getPropertyNameExpr() and init = any(ForStmt f).getInit()
135+
|
136+
i.getVariable().getADefinition() = init or
137+
i.getVariable().getADefinition().(VariableDeclarator).getDeclStmt() = init
138+
)
139+
}
140+
141+
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
142+
prop = arrayElement() and
143+
obj = this.(DataFlow::PropRead).getBase() and
144+
element = this
145+
}
146+
147+
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
148+
prop = arrayElement() and
149+
element = this.(DataFlow::PropWrite).getRhs() and
150+
this = obj.(DataFlow::SourceNode).getAPropertyWrite()
151+
}
152+
}
153+
154+
/**
155+
* A step for retrieving an element from an array using `.pop()` or `.shift()`.
156+
* E.g. `array.pop()`.
157+
*/
158+
private class ArrayPopStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
159+
ArrayPopStep() {
160+
getMethodName() = "pop" or
161+
getMethodName() = "shift"
162+
}
163+
164+
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
165+
prop = arrayElement() and
166+
obj = this.getReceiver() and
167+
element = this
168+
}
169+
}
170+
171+
/**
172+
* A step for iterating an array using `map` or `forEach`.
173+
*
174+
* Array elements can be loaded from the array `arr` to `e` in e.g: `arr.forEach(e => ...)`.
175+
*
176+
* And array elements can be stored into a resulting array using `map(...)`.
177+
* E.g. in `arr.map(e => foo)`, the resulting array (`arr.map(e => foo)`) will contain the element `foo`.
178+
*
179+
* And the second parameter in the callback is the array ifself, so there is a `loadStoreStep` from the array to that second parameter.
180+
*/
181+
private class ArrayIteration extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
182+
ArrayIteration() {
183+
this.getMethodName() = "map" or
184+
this.getMethodName() = "forEach"
185+
}
186+
187+
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
188+
prop = arrayElement() and
189+
obj = this.getReceiver() and
190+
element = getCallback(0).getParameter(0)
191+
}
192+
193+
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
194+
this.getMethodName() = "map" and
195+
prop = arrayElement() and
196+
element = this.getCallback(0).getAReturn() and
197+
obj = this
198+
}
199+
200+
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
201+
prop = arrayElement() and
202+
pred = this.getReceiver() and
203+
succ = getCallback(0).getParameter(2)
204+
}
205+
}
206+
207+
/**
208+
* A step for creating an array and storing the elements in the array.
209+
*/
210+
private class ArrayCreationStep extends DataFlow::AdditionalFlowStep, DataFlow::Node {
211+
ArrayCreationStep() { this instanceof DataFlow::ArrayCreationNode }
212+
213+
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
214+
prop = arrayElement() and
215+
element = this.(DataFlow::ArrayCreationNode).getAnElement() and
216+
obj = this
217+
}
218+
}
219+
220+
/**
221+
* A step modelling that `splice` can insert elements into an array.
222+
* For example in `array.splice(i, del, e)`: if `e` is tainted, then so is `array
223+
*/
224+
private class ArraySpliceStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
225+
ArraySpliceStep() { this.getMethodName() = "splice" }
226+
227+
override predicate storeStep(DataFlow::Node element, DataFlow::Node obj, string prop) {
228+
prop = arrayElement() and
229+
element = getArgument(2) and
230+
obj = this.getReceiver().getALocalSource()
231+
}
232+
}
233+
234+
/**
235+
* A step for modelling `concat`.
236+
* For example in `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
237+
*/
238+
private class ArrayConcatStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
239+
ArrayConcatStep() { this.getMethodName() = "concat" }
240+
241+
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
242+
prop = arrayElement() and
243+
(pred = this.getReceiver() or pred = this.getAnArgument()) and
244+
succ = this
245+
}
246+
}
247+
248+
/**
249+
* A step for modelling that elements from an array `arr` also appear in the result from calling `slice`/`splice`/`filter`.
250+
*/
251+
private class ArraySliceStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode {
252+
ArraySliceStep() {
253+
this.getMethodName() = "slice" or
254+
this.getMethodName() = "splice" or
255+
this.getMethodName() = "filter"
256+
}
257+
258+
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
259+
prop = arrayElement() and
260+
pred = this.getReceiver() and
261+
succ = this
262+
}
263+
}
264+
}

javascript/ql/src/semmle/javascript/dataflow/Nodes.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,8 +587,8 @@ class ArrayConstructorInvokeNode extends DataFlow::InvokeNode {
587587
}
588588

589589
/**
590-
* A data flow node corresponding to the creation or a new array, either through an array literal
591-
* or an invocation of the `Array` constructor.
590+
* A data flow node corresponding to the creation or a new array, either through an array literal,
591+
* an invocation of the `Array` constructor, or the `Array.from` method.
592592
*
593593
*
594594
* Examples:

javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll

Lines changed: 1 addition & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -282,92 +282,7 @@ module TaintTracking {
282282
}
283283
}
284284

285-
/**
286-
* A taint propagating data flow edge caused by the builtin array functions.
287-
*/
288-
private class ArrayFunctionTaintStep extends AdditionalTaintStep {
289-
DataFlow::CallNode call;
290-
291-
ArrayFunctionTaintStep() { this = call }
292-
293-
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
294-
arrayFunctionTaintStep(pred, succ, call)
295-
}
296-
}
297-
298-
/**
299-
* A taint propagating data flow edge from `pred` to `succ` caused by a call `call` to a builtin array functions.
300-
*/
301-
predicate arrayFunctionTaintStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode call) {
302-
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
303-
// `elt` and `ary`; similar for `forEach`
304-
exists(string name, Function f, int i |
305-
(name = "map" or name = "forEach") and
306-
(i = 0 or i = 2) and
307-
call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and
308-
call.(DataFlow::MethodCallNode).getMethodName() = name and
309-
pred = call.getReceiver() and
310-
succ = DataFlow::parameterNode(f.getParameter(i))
311-
)
312-
or
313-
// `array.map` with tainted return value in callback
314-
exists(DataFlow::FunctionNode f |
315-
call.(DataFlow::MethodCallNode).getMethodName() = "map" and
316-
call.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow
317-
pred = f.getAReturn() and
318-
succ = call
319-
)
320-
or
321-
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
322-
exists(string name |
323-
name = "push" or
324-
name = "unshift"
325-
|
326-
pred = call.getAnArgument() and
327-
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
328-
)
329-
or
330-
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
331-
exists(string name |
332-
name = "push" or
333-
name = "unshift"
334-
|
335-
pred = call.getASpreadArgument() and
336-
// Make sure we handle reflective calls
337-
succ = call.getReceiver().getALocalSource() and
338-
call.getCalleeName() = name
339-
)
340-
or
341-
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
342-
exists(string name | name = "splice" |
343-
pred = call.getArgument(2) and
344-
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
345-
)
346-
or
347-
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
348-
exists(string name |
349-
name = "pop" or
350-
name = "shift" or
351-
name = "slice" or
352-
name = "splice"
353-
|
354-
call.(DataFlow::MethodCallNode).calls(pred, name) and
355-
succ = call
356-
)
357-
or
358-
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
359-
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and
360-
pred = call.getAnArgument() and
361-
succ = call
362-
or
363-
// `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
364-
call.(DataFlow::MethodCallNode).calls(pred, "concat") and
365-
succ = call
366-
or
367-
call.(DataFlow::MethodCallNode).getMethodName() = "concat" and
368-
succ = call and
369-
pred = call.getAnArgument()
370-
}
285+
predicate arrayFunctionTaintStep = ArrayTaintTracking::arrayFunctionTaintStep/3;
371286

372287
/**
373288
* A taint propagating data flow edge for assignments of the form `o[k] = v`, where
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
| arrays.js:2:16:2:23 | "source" | arrays.js:5:8:5:14 | obj.foo |
2+
| arrays.js:2:16:2:23 | "source" | arrays.js:11:10:11:15 | arr[i] |
3+
| arrays.js:2:16:2:23 | "source" | arrays.js:15:27:15:27 | e |
4+
| arrays.js:2:16:2:23 | "source" | arrays.js:16:23:16:23 | e |
5+
| arrays.js:2:16:2:23 | "source" | arrays.js:20:8:20:16 | arr.pop() |
6+
| arrays.js:18:22:18:29 | "source" | arrays.js:18:50:18:50 | e |
7+
| arrays.js:22:15:22:22 | "source" | arrays.js:23:8:23:17 | arr2.pop() |
8+
| arrays.js:25:15:25:22 | "source" | arrays.js:26:8:26:17 | arr3.pop() |
9+
| arrays.js:29:21:29:28 | "source" | arrays.js:30:8:30:17 | arr4.pop() |
10+
| arrays.js:29:21:29:28 | "source" | arrays.js:33:8:33:17 | arr5.pop() |
11+
| arrays.js:29:21:29:28 | "source" | arrays.js:35:8:35:26 | arr5.slice(2).pop() |
12+
| arrays.js:29:21:29:28 | "source" | arrays.js:41:8:41:17 | arr6.pop() |
13+
| arrays.js:44:4:44:11 | "source" | arrays.js:45:10:45:18 | ary.pop() |

0 commit comments

Comments
 (0)