Skip to content

Commit f4f8ce0

Browse files
authored
Merge pull request github#6294 from erik-krogh/arrify
Approved by asgerf
2 parents 8ef5736 + 2b6790e commit f4f8ce0

File tree

7 files changed

+441
-75
lines changed

7 files changed

+441
-75
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
lgtm,codescanning
2+
* The dataflow libraries now model dataflow through more array libraries.
3+
Affected packages are
4+
[array-from](https://npmjs.com/package/array-from),
5+
[array.prototype.find](https://npmjs.com/package/array.prototype.find),
6+
[array-find](https://npmjs.com/package/array-find),
7+
[arrify](https://npmjs.com/package/arrify),
8+
[array-ify](https://npmjs.com/package/array-ify),
9+
[array-union](https://npmjs.com/package/array-union),
10+
[array-uniq](https://npmjs.com/package/array-uniq),
11+
[uniq](https://npmjs.com/package/uniq),
12+
[array-flatten](https://npmjs.com/package/array-flatten),
13+
[arr-flatten](https://npmjs.com/package/arr-flatten),
14+
[flatten](https://npmjs.com/package/flatten),
15+
[array.prototype.flat](https://npmjs.com/package/array.prototype.flat)

javascript/ql/src/semmle/javascript/Arrays.qll

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ module ArrayTaintTracking {
6868
succ = call
6969
or
7070
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
71-
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and
71+
call = arrayFromCall() and
7272
pred = call.getAnArgument() and
7373
succ = call
7474
or
@@ -79,6 +79,11 @@ module ArrayTaintTracking {
7979
call.(DataFlow::MethodCallNode).getMethodName() = "concat" and
8080
succ = call and
8181
pred = call.getAnArgument()
82+
or
83+
// find
84+
// `e = arr.find(callback)`
85+
call = arrayFindCall(pred) and
86+
succ = call
8287
}
8388
}
8489

@@ -97,7 +102,7 @@ private module ArrayDataFlow {
97102
DataFlow::Node pred, DataFlow::Node succ, string fromProp, string toProp
98103
) {
99104
exists(DataFlow::CallNode call |
100-
call = DataFlow::globalVarRef("Array").getAMemberCall("from") and
105+
call = arrayFromCall() and
101106
pred = call.getArgument(0) and
102107
succ = call and
103108
fromProp = arrayLikeElement() and
@@ -297,4 +302,108 @@ private module ArrayDataFlow {
297302
)
298303
}
299304
}
305+
306+
/**
307+
* A step modelling that elements from an array `arr` are received by calling `find`.
308+
*/
309+
private class ArrayFindStep extends DataFlow::SharedFlowStep {
310+
override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
311+
exists(DataFlow::CallNode call |
312+
call = arrayFindCall(pred) and
313+
succ = call and
314+
prop = arrayElement()
315+
)
316+
}
317+
}
318+
}
319+
320+
private import ArrayLibraries
321+
322+
/**
323+
* Classes and predicates modelling various libraries that work on arrays or array-like structures.
324+
*/
325+
private module ArrayLibraries {
326+
private import DataFlow::PseudoProperties
327+
328+
/**
329+
* Gets a call to `Array.from` or a polyfill implementing the same functionality.
330+
*/
331+
DataFlow::CallNode arrayFromCall() {
332+
result = DataFlow::globalVarRef("Array").getAMemberCall("from")
333+
or
334+
result = DataFlow::moduleImport("array-from").getACall()
335+
}
336+
337+
/**
338+
* Gets a call to `Array.prototype.find` or a polyfill implementing the same functionality.
339+
*/
340+
DataFlow::CallNode arrayFindCall(DataFlow::Node array) {
341+
result.(DataFlow::MethodCallNode).getMethodName() = "find" and
342+
array = result.getReceiver()
343+
or
344+
result = DataFlow::moduleImport(["array.prototype.find", "array-find"]).getACall() and
345+
array = result.getArgument(0)
346+
}
347+
348+
/**
349+
* A taint step through the `arrify` library, or other libraries that (maybe) convert values into arrays.
350+
*/
351+
private class ArrayifyStep extends TaintTracking::SharedTaintStep {
352+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
353+
exists(API::CallNode call | call = API::moduleImport(["arrify", "array-ify"]).getACall() |
354+
pred = call.getArgument(0) and succ = call
355+
)
356+
}
357+
}
358+
359+
/**
360+
* A call to a library that copies the elements of an array into another array.
361+
* E.g. `array-union` that creates a union of multiple arrays, or `array-uniq` that creates an array with unique elements.
362+
*/
363+
DataFlow::CallNode arrayCopyCall(DataFlow::Node array) {
364+
result = API::moduleImport(["array-union", "array-uniq", "uniq"]).getACall() and
365+
array = result.getAnArgument()
366+
}
367+
368+
/**
369+
* A taint step for a library that copies the elements of an array into another array.
370+
*/
371+
private class ArrayCopyTaint extends TaintTracking::SharedTaintStep {
372+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
373+
exists(DataFlow::CallNode call |
374+
call = arrayCopyCall(pred) and
375+
succ = call
376+
)
377+
}
378+
}
379+
380+
/**
381+
* A loadStoreStep for a library that copies the elements of an array into another array.
382+
*/
383+
private class ArrayCopyLoadStore extends DataFlow::SharedFlowStep {
384+
override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) {
385+
exists(DataFlow::CallNode call |
386+
call = arrayCopyCall(pred) and
387+
succ = call and
388+
prop = arrayElement()
389+
)
390+
}
391+
}
392+
393+
/**
394+
* A taint step through a call to `Array.prototype.flat` or a polyfill implementing array flattening.
395+
*/
396+
private class ArrayFlatStep extends TaintTracking::SharedTaintStep {
397+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
398+
exists(DataFlow::CallNode call | succ = call |
399+
call.(DataFlow::MethodCallNode).getMethodName() = "flat" and
400+
pred = call.getReceiver()
401+
or
402+
call =
403+
API::moduleImport(["array-flatten", "arr-flatten", "flatten", "array.prototype.flat"])
404+
.getACall() and
405+
pred = call.getAnArgument()
406+
)
407+
}
408+
}
300409
}

javascript/ql/test/library-tests/Arrays/DataFlow.expected

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
| arrays.js:2:16:2:23 | "source" | arrays.js:56:10:56:10 | x |
88
| arrays.js:2:16:2:23 | "source" | arrays.js:60:10:60:10 | x |
99
| arrays.js:2:16:2:23 | "source" | arrays.js:66:10:66:10 | x |
10+
| arrays.js:2:16:2:23 | "source" | arrays.js:71:10:71:10 | x |
11+
| arrays.js:2:16:2:23 | "source" | arrays.js:74:8:74:29 | arr.fin ... llback) |
12+
| arrays.js:2:16:2:23 | "source" | arrays.js:77:8:77:35 | arrayFi ... llback) |
13+
| arrays.js:2:16:2:23 | "source" | arrays.js:81:10:81:10 | x |
1014
| arrays.js:18:22:18:29 | "source" | arrays.js:18:50:18:50 | e |
1115
| arrays.js:22:15:22:22 | "source" | arrays.js:23:8:23:17 | arr2.pop() |
1216
| arrays.js:25:15:25:22 | "source" | arrays.js:26:8:26:17 | arr3.pop() |

javascript/ql/test/library-tests/Arrays/arrays.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,19 @@
6565
for (const x of arr7) {
6666
sink(x); // NOT OK
6767
}
68+
69+
const arrayFrom = require("array-from");
70+
for (const x of arrayFrom(arr)) {
71+
sink(x); // NOT OK
72+
}
73+
74+
sink(arr.find(someCallback)); // NOT OK
75+
76+
const arrayFind = require("array-find");
77+
sink(arrayFind(arr, someCallback)); // NOT OK
78+
79+
const uniq = require("uniq");
80+
for (const x of uniq(arr)) {
81+
sink(x); // NOT OK
82+
}
6883
});

0 commit comments

Comments
 (0)