Skip to content

Commit 4389b5c

Browse files
committed
JS: Fix issue for .apply() calls
1 parent 34e6864 commit 4389b5c

5 files changed

Lines changed: 41 additions & 5 deletions

File tree

javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowNode.qll

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ private module Cached {
5555
invoke.isSpreadArgument(_) and
5656
storeContent = dynamicArgumentsContent()
5757
} or
58+
TApplyCallTaintNode(MethodCallExpr node) {
59+
node.getMethodName() = "apply" and exists(node.getArgument(1))
60+
} or
5861
TDestructuredModuleImportNode(ImportDeclaration decl) {
5962
exists(decl.getASpecifier().getImportedName())
6063
} or

javascript/ql/lib/semmle/javascript/dataflow/internal/DataFlowPrivate.qll

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,28 @@ class DynamicParameterArrayNode extends DataFlow::Node, TDynamicParameterArrayNo
197197
override Location getLocation() { result = function.getLocation() }
198198
}
199199

200+
/**
201+
* Node with taint input from the second argument of `.apply()` and with a store edge back into that same argument.
202+
*
203+
* This ensures that if `.apply()` is called with a tainted value (not inside a content) the taint is
204+
* boxed in an `ArrayElement` content. This is necessary for the target function to propagate the taint.
205+
*/
206+
class ApplyCallTaintNode extends DataFlow::Node, TApplyCallTaintNode {
207+
private MethodCallExpr apply;
208+
209+
ApplyCallTaintNode() { this = TApplyCallTaintNode(apply) }
210+
211+
override StmtContainer getContainer() { result = apply.getContainer() }
212+
213+
override string toString() { result = "[apply call taint node]" }
214+
215+
override Location getLocation() { result = apply.getArgument(1).getLocation() }
216+
217+
MethodCallExpr getMethodCallExpr() { result = apply }
218+
219+
DataFlow::Node getArrayNode() { result = apply.getArgument(1).flow() }
220+
}
221+
200222
cached
201223
newtype TReturnKind =
202224
MkNormalReturnKind() or
@@ -1233,6 +1255,12 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
12331255
c.asArrayIndex() = n and
12341256
not n >= firstSpreadArgumentIndex(invoke)
12351257
)
1258+
or
1259+
exists(ApplyCallTaintNode taintNode |
1260+
node1 = taintNode and
1261+
node2 = taintNode.getArrayNode() and
1262+
c = ContentSet::arrayElementUnknown()
1263+
)
12361264
}
12371265

12381266
/**

javascript/ql/lib/semmle/javascript/dataflow/internal/TaintTrackingPrivate.qll

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ predicate defaultAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2)
2525
node1 = TValueNode(invoke.getAnArgument().stripParens().(SpreadElement).getOperand()) and
2626
node2 = TDynamicArgumentStoreNode(invoke, c) and
2727
c.isUnknownArrayElement()
28-
// TODO: we need a similar case for .apply() calls
28+
)
29+
or
30+
// If the array in an .apply() call is tainted (not inside a content), box it in an array element (similar to the case above).
31+
exists(ApplyCallTaintNode taintNode |
32+
node1 = taintNode.getArrayNode() and
33+
node2 = taintNode
2934
)
3035
}
3136

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
legacyDataFlowDifference
2-
| array-mutation.js:31:33:31:40 | source() | array-mutation.js:32:8:32:8 | h | only flow with OLD data flow library |
3-
| array-mutation.js:35:36:35:43 | source() | array-mutation.js:36:8:36:8 | i | only flow with OLD data flow library |
42
| arrays-init.js:2:16:2:23 | source() | arrays-init.js:27:8:27:13 | arr[0] | only flow with OLD data flow library |
53
| arrays-init.js:2:16:2:23 | source() | arrays-init.js:33:8:33:13 | arr[0] | only flow with OLD data flow library |
64
| arrays-init.js:2:16:2:23 | source() | arrays-init.js:35:8:35:13 | arr[2] | only flow with OLD data flow library |
@@ -47,6 +45,8 @@ flow
4745
| array-mutation.js:19:18:19:25 | source() | array-mutation.js:20:8:20:8 | e |
4846
| array-mutation.js:23:13:23:20 | source() | array-mutation.js:24:8:24:8 | f |
4947
| array-mutation.js:27:16:27:23 | source() | array-mutation.js:28:8:28:8 | g |
48+
| array-mutation.js:31:33:31:40 | source() | array-mutation.js:32:8:32:8 | h |
49+
| array-mutation.js:35:36:35:43 | source() | array-mutation.js:36:8:36:8 | i |
5050
| array-mutation.js:39:17:39:24 | source() | array-mutation.js:40:8:40:8 | j |
5151
| arrays-init.js:2:16:2:23 | source() | arrays-init.js:17:8:17:13 | arr[1] |
5252
| arrays-init.js:2:16:2:23 | source() | arrays-init.js:22:8:22:13 | arr[6] |

javascript/ql/test/library-tests/TaintTracking/array-mutation.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ function test(x, y) {
2929

3030
let h = [];
3131
Array.prototype.push.apply(h, source());
32-
sink(h); // NOT OK [INCONSISTENCY]
32+
sink(h); // NOT OK
3333

3434
let i = [];
3535
Array.prototype.unshift.apply(i, source());
36-
sink(i); // NOT OK [INCONSISTENCY]
36+
sink(i); // NOT OK
3737

3838
let j = [];
3939
j[j.length] = source();

0 commit comments

Comments
 (0)