Skip to content

Commit abbadf2

Browse files
authored
Merge pull request #192 from esben-semmle/js/additional-array-taint-steps
Approved by asger-semmle
2 parents 961ecfb + cb2bd9e commit abbadf2

File tree

4 files changed

+102
-30
lines changed

4 files changed

+102
-30
lines changed

change-notes/1.19/analysis-javascript.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## General improvements
44

5+
* Modelling of taint flow through array operations has been improved. This may give additional results for the security queries.
6+
57
## New queries
68

79
| **Query** | **Tags** | **Purpose** |

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

Lines changed: 62 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -185,38 +185,15 @@ module TaintTracking {
185185

186186
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
187187
succ = this and
188-
(
189-
exists (Expr e, Expr f | e = this.asExpr() and f = pred.asExpr() |
190-
// arrays with tainted elements and objects with tainted property names are tainted
191-
e.(ArrayExpr).getAnElement() = f or
192-
exists (Property prop | e.(ObjectExpr).getAProperty() = prop |
193-
prop.isComputed() and f = prop.getNameExpr()
194-
)
195-
or
196-
// awaiting a tainted expression gives a tainted result
197-
e.(AwaitExpr).getOperand() = f
188+
exists (Expr e, Expr f | e = this.asExpr() and f = pred.asExpr() |
189+
// arrays with tainted elements and objects with tainted property names are tainted
190+
e.(ArrayExpr).getAnElement() = f or
191+
exists (Property prop | e.(ObjectExpr).getAProperty() = prop |
192+
prop.isComputed() and f = prop.getNameExpr()
198193
)
199194
or
200-
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
201-
// `elt` and `ary`; similar for `forEach`
202-
exists (MethodCallExpr m, Function f, int i, SimpleParameter p |
203-
(m.getMethodName() = "map" or m.getMethodName() = "forEach") and
204-
(i = 0 or i = 2) and
205-
m.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and
206-
p = f.getParameter(i) and
207-
this = DataFlow::parameterNode(p) and
208-
pred.asExpr() = m.getReceiver()
209-
)
210-
or
211-
// `array.map` with tainted return value in callback
212-
exists (MethodCallExpr m, Function f |
213-
this.asExpr() = m and
214-
m.getMethodName() = "map" and
215-
m.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow
216-
pred = f.getAReturnedExpr().flow())
217-
or
218-
// `array.push(e)`: if `e` is tainted, then so is `array`
219-
succ.(DataFlow::SourceNode).getAMethodCall("push").getAnArgument() = pred
195+
// awaiting a tainted expression gives a tainted result
196+
e.(AwaitExpr).getOperand() = f
220197
)
221198
or
222199
// reading from a tainted object yields a tainted result
@@ -233,6 +210,61 @@ module TaintTracking {
233210
}
234211
}
235212

213+
/**
214+
* A taint propagating data flow edge caused by the builtin array functions.
215+
*/
216+
private class ArrayFunctionTaintStep extends AdditionalTaintStep {
217+
DataFlow::CallNode call;
218+
219+
ArrayFunctionTaintStep() {
220+
this = call
221+
}
222+
223+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
224+
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
225+
// `elt` and `ary`; similar for `forEach`
226+
exists (string name, Function f, int i |
227+
(name = "map" or name = "forEach") and
228+
(i = 0 or i = 2) and
229+
call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and
230+
pred.(DataFlow::SourceNode).getAMethodCall(name) = call and
231+
succ = DataFlow::parameterNode(f.getParameter(i))
232+
)
233+
or
234+
// `array.map` with tainted return value in callback
235+
exists (DataFlow::FunctionNode f |
236+
call.(DataFlow::MethodCallNode).getMethodName() = "map" and
237+
call.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow
238+
pred = f.getAReturn() and
239+
succ = call
240+
)
241+
or
242+
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
243+
exists (string name |
244+
name = "push" or
245+
name = "unshift" |
246+
pred = call.getAnArgument() and
247+
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
248+
)
249+
or
250+
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
251+
exists (string name |
252+
name = "pop" or
253+
name = "shift" or
254+
name = "slice" or
255+
name = "splice" |
256+
call.(DataFlow::MethodCallNode).calls(pred, name) and
257+
succ = call
258+
)
259+
or
260+
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
261+
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and
262+
pred = call.getAnArgument() and
263+
succ = call
264+
}
265+
266+
}
267+
236268
/**
237269
* A taint propagating data flow edge for assignments of the form `o[k] = v`, where
238270
* `k` is not a constant and `o` refers to some object literal; in this case, we consider

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,15 @@
33
| tst.js:2:13:2:20 | source() | tst.js:14:10:14:17 | x.sort() |
44
| tst.js:2:13:2:20 | source() | tst.js:17:10:17:10 | a |
55
| tst.js:2:13:2:20 | source() | tst.js:19:10:19:10 | a |
6+
| tst.js:2:13:2:20 | source() | tst.js:23:10:23:10 | b |
7+
| tst.js:2:13:2:20 | source() | tst.js:25:10:25:16 | x.pop() |
8+
| tst.js:2:13:2:20 | source() | tst.js:26:10:26:18 | x.shift() |
9+
| tst.js:2:13:2:20 | source() | tst.js:27:10:27:18 | x.slice() |
10+
| tst.js:2:13:2:20 | source() | tst.js:28:10:28:19 | x.splice() |
11+
| tst.js:2:13:2:20 | source() | tst.js:30:10:30:22 | Array.from(x) |
12+
| tst.js:2:13:2:20 | source() | tst.js:33:14:33:16 | elt |
13+
| tst.js:2:13:2:20 | source() | tst.js:35:14:35:16 | ary |
14+
| tst.js:2:13:2:20 | source() | tst.js:39:14:39:16 | elt |
15+
| tst.js:2:13:2:20 | source() | tst.js:41:14:41:16 | ary |
16+
| tst.js:2:13:2:20 | source() | tst.js:44:10:44:30 | innocen ... ) => x) |
17+
| tst.js:2:13:2:20 | source() | tst.js:45:10:45:24 | x.map(x2 => x2) |

javascript/ql/test/library-tests/TaintTracking/tst.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,30 @@ function test() {
1818
a.push(x);
1919
sink(a); // NOT OK
2020

21+
var b = [];
22+
b.unshift(x);
23+
sink(b); // NOT OK
24+
25+
sink(x.pop()); // NOT OK
26+
sink(x.shift()); // NOT OK
27+
sink(x.slice()); // NOT OK
28+
sink(x.splice()); // NOT OK
29+
30+
sink(Array.from(x)); // NOT OK
31+
32+
x.map((elt, i, ary) => {
33+
sink(elt); // NOT OK
34+
sink(i); // OK
35+
sink(ary); // NOT OK
36+
});
37+
38+
x.forEach((elt, i, ary) => {
39+
sink(elt); // NOT OK
40+
sink(i); // OK
41+
sink(ary); // NOT OK
42+
});
43+
44+
sink(innocent.map(() => x)); // NOT OK
45+
sink(x.map(x2 => x2)); // NOT OK
46+
2147
}

0 commit comments

Comments
 (0)