Skip to content

Commit 40f6dc1

Browse files
authored
Merge pull request #1578 from asger-semmle/splice
Approved by xiemaisi
2 parents 463547f + 3026553 commit 40f6dc1

File tree

5 files changed

+97
-1
lines changed

5 files changed

+97
-1
lines changed

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,12 @@ module DataFlow {
841841
/** Gets the data flow node corresponding to an argument of this invocation. */
842842
abstract DataFlow::Node getAnArgument();
843843

844+
/**
845+
* Gets a data flow node corresponding to an array of values being passed as
846+
* individual arguments to this invocation.
847+
*/
848+
abstract DataFlow::Node getASpreadArgument();
849+
844850
/** Gets the number of arguments of this invocation, if it can be determined. */
845851
abstract int getNumArgument();
846852
}
@@ -889,6 +895,12 @@ module DataFlow {
889895
)
890896
}
891897

898+
override DataFlow::Node getASpreadArgument() {
899+
exists(SpreadElement arg | arg = astNode.getAnArgument() |
900+
result = DataFlow::valueNode(arg.getOperand())
901+
)
902+
}
903+
892904
override int getNumArgument() {
893905
not astNode.isSpreadArgument(_) and result = astNode.getNumArgument()
894906
}
@@ -929,7 +941,9 @@ module DataFlow {
929941

930942
ReflectiveCallNodeDef() { this = TReflectiveCallNode(originalCall.asExpr(), kind) }
931943

932-
override string getCalleeName() { none() }
944+
override string getCalleeName() {
945+
result = originalCall.getReceiver().asExpr().(PropAccess).getPropertyName()
946+
}
933947

934948
override DataFlow::Node getCalleeNode() { result = originalCall.getReceiver() }
935949

@@ -943,6 +957,14 @@ module DataFlow {
943957
kind = "call" and result = originalCall.getAnArgument() and result != getReceiver()
944958
}
945959

960+
override DataFlow::Node getASpreadArgument() {
961+
kind = "apply" and
962+
result = originalCall.getArgument(1)
963+
or
964+
kind = "call" and
965+
result = originalCall.getASpreadArgument()
966+
}
967+
946968
override int getNumArgument() {
947969
result >= 0 and kind = "call" and result = originalCall.getNumArgument() - 1
948970
}

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,19 @@ class InvokeNode extends DataFlow::SourceNode {
6767
/** Gets the data flow node corresponding to the last argument of this invocation. */
6868
DataFlow::Node getLastArgument() { result = getArgument(getNumArgument() - 1) }
6969

70+
/**
71+
* Gets a data flow node corresponding to an array of values being passed as
72+
* individual arguments to this invocation.
73+
*
74+
* Examples:
75+
* ```
76+
* x.push(...args); // 'args' is a spread argument
77+
* x.push(x, ...args, y, ...more); // 'args' and 'more' are a spread arguments
78+
* Array.prototype.push.apply(x, args); // 'args' is a spread argument
79+
* ```
80+
.*/
81+
DataFlow::Node getASpreadArgument() { result = impl.getASpreadArgument() }
82+
7083
/** Gets the number of arguments of this invocation, if it can be determined. */
7184
int getNumArgument() { result = impl.getNumArgument() }
7285

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,25 @@ module TaintTracking {
290290
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
291291
)
292292
or
293+
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
294+
exists(string name |
295+
name = "push" or
296+
name = "unshift"
297+
|
298+
pred = call.getASpreadArgument() and
299+
// Make sure we handle reflective calls
300+
succ = call.getReceiver().getALocalSource() and
301+
call.getCalleeName() = name
302+
)
303+
or
304+
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
305+
exists(string name |
306+
name = "splice"
307+
|
308+
pred = call.getArgument(2) and
309+
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
310+
)
311+
or
293312
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
294313
exists(string name |
295314
name = "pop" or

javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ typeInferenceMismatch
99
| addexpr.js:11:15:11:22 | source() | addexpr.js:21:8:21:12 | value |
1010
| advanced-callgraph.js:2:13:2:20 | source() | advanced-callgraph.js:6:22:6:22 | v |
1111
| array-callback.js:2:23:2:30 | source() | array-callback.js:4:10:4:10 | x |
12+
| array-mutation.js:19:18:19:25 | source() | array-mutation.js:20:8:20:8 | e |
13+
| array-mutation.js:23:13:23:20 | source() | array-mutation.js:24:8:24:8 | f |
14+
| array-mutation.js:27:16:27:23 | source() | array-mutation.js:28:8:28:8 | g |
15+
| array-mutation.js:31:33:31:40 | source() | array-mutation.js:32:8:32:8 | h |
16+
| array-mutation.js:35:36:35:43 | source() | array-mutation.js:36:8:36:8 | i |
1217
| booleanOps.js:2:11:2:18 | source() | booleanOps.js:4:8:4:8 | x |
1318
| booleanOps.js:2:11:2:18 | source() | booleanOps.js:13:10:13:10 | x |
1419
| booleanOps.js:2:11:2:18 | source() | booleanOps.js:19:10:19:10 | x |
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
function test(x, y) {
2+
let a = [];
3+
a.splice(source(), x);
4+
sink(a); // OK
5+
6+
let b = [];
7+
b.splice(x, source());
8+
sink(b); // OK
9+
10+
let c = [];
11+
c.splice(source(), x, y);
12+
sink(c); // OK
13+
14+
let d = [];
15+
d.splice(x, source(), y);
16+
sink(d); // OK
17+
18+
let e = [];
19+
e.splice(x, y, source());
20+
sink(e); // NOT OK
21+
22+
let f = [];
23+
f.push(...source());
24+
sink(f); // NOT OK
25+
26+
let g = [];
27+
g.unshift(...source());
28+
sink(g); // NOT OK
29+
30+
let h = [];
31+
Array.prototype.push.apply(h, source());
32+
sink(h); // NOT OK
33+
34+
let i = [];
35+
Array.prototype.unshift.apply(i, source());
36+
sink(i); // NOT OK
37+
}

0 commit comments

Comments
 (0)