Skip to content

Commit dd9e3d2

Browse files
committed
expose TaintTracking::arrayFunctionTaintStep and add a step for "concat"
1 parent 8ea6070 commit dd9e3d2

File tree

1 file changed

+74
-59
lines changed

1 file changed

+74
-59
lines changed

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

Lines changed: 74 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -271,67 +271,82 @@ module TaintTracking {
271271
ArrayFunctionTaintStep() { this = call }
272272

273273
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
274-
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
275-
// `elt` and `ary`; similar for `forEach`
276-
exists(string name, Function f, int i |
277-
(name = "map" or name = "forEach") and
278-
(i = 0 or i = 2) and
279-
call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and
280-
call.(DataFlow::MethodCallNode).getMethodName() = name and
281-
pred = call.getReceiver() and
282-
succ = DataFlow::parameterNode(f.getParameter(i))
283-
)
284-
or
285-
// `array.map` with tainted return value in callback
286-
exists(DataFlow::FunctionNode f |
287-
call.(DataFlow::MethodCallNode).getMethodName() = "map" and
288-
call.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow
289-
pred = f.getAReturn() and
290-
succ = call
291-
)
292-
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.getAnArgument() and
299-
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
300-
)
301-
or
302-
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
303-
exists(string name |
304-
name = "push" or
305-
name = "unshift"
306-
|
307-
pred = call.getASpreadArgument() and
308-
// Make sure we handle reflective calls
309-
succ = call.getReceiver().getALocalSource() and
310-
call.getCalleeName() = name
311-
)
312-
or
313-
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
314-
exists(string name | name = "splice" |
315-
pred = call.getArgument(2) and
316-
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
317-
)
318-
or
319-
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
320-
exists(string name |
321-
name = "pop" or
322-
name = "shift" or
323-
name = "slice" or
324-
name = "splice"
325-
|
326-
call.(DataFlow::MethodCallNode).calls(pred, name) and
327-
succ = call
328-
)
329-
or
330-
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
331-
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and
274+
arrayFunctionTaintStep(pred, succ, call)
275+
}
276+
}
277+
278+
/**
279+
* A taint propagating data flow edge from `pred` to `succ` caused by a call `call` to a builtin array functions.
280+
*/
281+
predicate arrayFunctionTaintStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::CallNode call) {
282+
// `array.map(function (elt, i, ary) { ... })`: if `array` is tainted, then so are
283+
// `elt` and `ary`; similar for `forEach`
284+
exists(string name, Function f, int i |
285+
(name = "map" or name = "forEach") and
286+
(i = 0 or i = 2) and
287+
call.getArgument(0).analyze().getAValue().(AbstractFunction).getFunction() = f and
288+
call.(DataFlow::MethodCallNode).getMethodName() = name and
289+
pred = call.getReceiver() and
290+
succ = DataFlow::parameterNode(f.getParameter(i))
291+
)
292+
or
293+
// `array.map` with tainted return value in callback
294+
exists(DataFlow::FunctionNode f |
295+
call.(DataFlow::MethodCallNode).getMethodName() = "map" and
296+
call.getArgument(0) = f and // Require the argument to be a closure to avoid spurious call/return flow
297+
pred = f.getAReturn() and
298+
succ = call
299+
)
300+
or
301+
// `array.push(e)`, `array.unshift(e)`: if `e` is tainted, then so is `array`.
302+
exists(string name |
303+
name = "push" or
304+
name = "unshift"
305+
|
332306
pred = call.getAnArgument() and
307+
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
308+
)
309+
or
310+
// `array.push(...e)`, `array.unshift(...e)`: if `e` is tainted, then so is `array`.
311+
exists(string name |
312+
name = "push" or
313+
name = "unshift"
314+
|
315+
pred = call.getASpreadArgument() and
316+
// Make sure we handle reflective calls
317+
succ = call.getReceiver().getALocalSource() and
318+
call.getCalleeName() = name
319+
)
320+
or
321+
// `array.splice(i, del, e)`: if `e` is tainted, then so is `array`.
322+
exists(string name | name = "splice" |
323+
pred = call.getArgument(2) and
324+
succ.(DataFlow::SourceNode).getAMethodCall(name) = call
325+
)
326+
or
327+
// `e = array.pop()`, `e = array.shift()`, or similar: if `array` is tainted, then so is `e`.
328+
exists(string name |
329+
name = "pop" or
330+
name = "shift" or
331+
name = "slice" or
332+
name = "splice"
333+
|
334+
call.(DataFlow::MethodCallNode).calls(pred, name) and
333335
succ = call
334-
}
336+
)
337+
or
338+
// `e = Array.from(x)`: if `x` is tainted, then so is `e`.
339+
call = DataFlow::globalVarRef("Array").getAPropertyRead("from").getACall() and
340+
pred = call.getAnArgument() and
341+
succ = call
342+
or
343+
// `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`.
344+
call.(DataFlow::MethodCallNode).calls(pred, "concat") and
345+
succ = call
346+
or
347+
call.(DataFlow::MethodCallNode).getMethodName() = "concat" and
348+
succ = call and
349+
pred = call.getAnArgument()
335350
}
336351

337352
/**

0 commit comments

Comments
 (0)