Skip to content

Commit 28d58be

Browse files
authored
Merge pull request #10126 from erik-krogh/myApply
JS: precise flow through calls to `.apply()`
2 parents cee1527 + a57981e commit 28d58be

26 files changed

+408
-83
lines changed

javascript/ql/lib/semmle/javascript/Arrays.qll

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -261,14 +261,12 @@ private module ArrayDataFlow {
261261
/**
262262
* A step for creating an array and storing the elements in the array.
263263
*/
264-
private class ArrayCreationStep extends DataFlow::SharedFlowStep {
264+
private class ArrayCreationStep extends PreCallGraphStep {
265265
override predicate storeStep(DataFlow::Node element, DataFlow::SourceNode obj, string prop) {
266266
exists(DataFlow::ArrayCreationNode array, int i |
267267
element = array.getElement(i) and
268268
obj = array and
269-
if array = any(PromiseAllCreation c).getArrayNode()
270-
then prop = arrayElement(i)
271-
else prop = arrayElement()
269+
prop = arrayElement(i)
272270
)
273271
}
274272
}
@@ -346,6 +344,14 @@ private module ArrayLibraries {
346344
result = DataFlow::globalVarRef("Array").getAMemberCall("from")
347345
or
348346
result = DataFlow::moduleImport("array-from").getACall()
347+
or
348+
// Array.prototype.slice.call acts the same as Array.from, and is sometimes used with e.g. the arguments object.
349+
result =
350+
DataFlow::globalVarRef("Array")
351+
.getAPropertyRead("prototype")
352+
.getAPropertyRead("slice")
353+
.getAMethodCall("call") and
354+
result.getNumArgument() = 1
349355
}
350356

351357
/**

javascript/ql/lib/semmle/javascript/PackageExports.qll

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,36 +11,14 @@ private import semmle.javascript.internal.CachedStages
1111
* Gets a parameter that is a library input to a top-level package.
1212
*/
1313
cached
14-
DataFlow::SourceNode getALibraryInputParameter() {
14+
DataFlow::Node getALibraryInputParameter() {
1515
Stages::Taint::ref() and
1616
exists(int bound, DataFlow::FunctionNode func |
1717
func = getAValueExportedByPackage().getABoundFunctionValue(bound)
1818
|
1919
result = func.getParameter(any(int arg | arg >= bound))
2020
or
21-
result = getAnArgumentsRead(func.getFunction())
22-
)
23-
}
24-
25-
private DataFlow::SourceNode getAnArgumentsRead(Function func) {
26-
exists(DataFlow::PropRead read |
27-
not read.getPropertyName() = "length" and
28-
result = read
29-
|
30-
read.getBase() = func.getArgumentsVariable().getAnAccess().flow()
31-
or
32-
exists(DataFlow::MethodCallNode call |
33-
call =
34-
DataFlow::globalVarRef("Array")
35-
.getAPropertyRead("prototype")
36-
.getAPropertyRead("slice")
37-
.getAMethodCall("call")
38-
or
39-
call = DataFlow::globalVarRef("Array").getAMethodCall("from")
40-
|
41-
call.getArgument(0) = func.getArgumentsVariable().getAnAccess().flow() and
42-
call.flowsTo(read.getBase())
43-
)
21+
result = func.getFunction().getArgumentsVariable().getAnAccess().flow()
4422
)
4523
}
4624

javascript/ql/lib/semmle/javascript/dataflow/Configuration.qll

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,56 +1267,47 @@ private predicate loadStep(
12671267
* If `onlyRelevantInCall` is true, the `base` object will not be propagated out of return edges, because
12681268
* the flow that originally reached `base.startProp` used a call edge.
12691269
*/
1270-
pragma[nomagic]
1270+
pragma[noopt]
12711271
private predicate reachableFromStoreBase(
12721272
string startProp, string endProp, DataFlow::Node base, DataFlow::Node nd,
1273-
DataFlow::Configuration cfg, PathSummary summary, boolean onlyRelevantInCall
1273+
DataFlow::Configuration cfg, TPathSummary summary, boolean onlyRelevantInCall
12741274
) {
1275-
exists(PathSummary s1, PathSummary s2, DataFlow::Node rhs |
1276-
reachableFromSource(rhs, cfg, s1) and
1277-
onlyRelevantInCall = s1.hasCall()
1278-
or
1279-
reachableFromStoreBase(_, _, _, rhs, cfg, s1, onlyRelevantInCall)
1280-
|
1275+
exists(TPathSummary s1, TPathSummary s2, DataFlow::Node rhs |
12811276
storeStep(rhs, nd, startProp, cfg, s2) and
12821277
endProp = startProp and
12831278
base = nd and
1284-
summary =
1285-
MkPathSummary(false, s2.hasCall(), DataFlow::FlowLabel::data(), DataFlow::FlowLabel::data())
1279+
exists(boolean hasCall, DataFlow::FlowLabel data |
1280+
hasCall = hasCall(s2) and
1281+
data = DataFlow::FlowLabel::data() and
1282+
summary = MkPathSummary(false, hasCall, data, data)
1283+
)
1284+
|
1285+
reachableFromSource(rhs, cfg, s1) and
1286+
onlyRelevantInCall = hasCall(s1)
1287+
or
1288+
reachableFromStoreBase(_, _, _, rhs, cfg, s1, onlyRelevantInCall)
12861289
)
12871290
or
1288-
exists(PathSummary newSummary, PathSummary oldSummary |
1289-
reachableFromStoreBaseStep(startProp, endProp, base, nd, cfg, oldSummary, newSummary,
1290-
onlyRelevantInCall) and
1291-
summary = oldSummary.appendValuePreserving(newSummary)
1292-
)
1293-
}
1294-
1295-
/**
1296-
* Holds if `base` is the base of a write to property `endProp`, and `nd` is reachable
1297-
* from `base` under configuration `cfg` (possibly through callees) along a path whose
1298-
* last step is summarized by `newSummary`, and the previous steps are summarized
1299-
* by `oldSummary`.
1300-
*/
1301-
pragma[noinline]
1302-
private predicate reachableFromStoreBaseStep(
1303-
string startProp, string endProp, DataFlow::Node base, DataFlow::Node nd,
1304-
DataFlow::Configuration cfg, PathSummary oldSummary, PathSummary newSummary,
1305-
boolean onlyRelevantInCall
1306-
) {
1307-
exists(DataFlow::Node mid |
1291+
exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary |
13081292
reachableFromStoreBase(startProp, endProp, base, mid, cfg, oldSummary, onlyRelevantInCall) and
13091293
flowStep(mid, cfg, nd, newSummary) and
1310-
onlyRelevantInCall.booleanAnd(newSummary.hasReturn()) = false
1294+
exists(boolean hasReturn |
1295+
hasReturn = newSummary.hasReturn() and
1296+
onlyRelevantInCall.booleanAnd(hasReturn) = false
1297+
)
13111298
or
13121299
exists(string midProp |
13131300
reachableFromStoreBase(startProp, midProp, base, mid, cfg, oldSummary, onlyRelevantInCall) and
13141301
isAdditionalLoadStoreStep(mid, nd, midProp, endProp, cfg) and
13151302
newSummary = PathSummary::level()
13161303
)
1304+
|
1305+
summary = oldSummary.appendValuePreserving(newSummary)
13171306
)
13181307
}
13191308

1309+
private boolean hasCall(PathSummary summary) { result = summary.hasCall() }
1310+
13201311
/**
13211312
* Holds if the value of `pred` is written to a property of some base object, and that base
13221313
* object may flow into the base of property read `succ` under configuration `cfg` along

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1029,6 +1029,32 @@ module DataFlow {
10291029
override File getFile() { result = function.getFile() }
10301030
}
10311031

1032+
/**
1033+
* A data flow node representing the arguments object given to a function.
1034+
*/
1035+
class ReflectiveParametersNode extends DataFlow::Node, TReflectiveParametersNode {
1036+
Function function;
1037+
1038+
ReflectiveParametersNode() { this = TReflectiveParametersNode(function) }
1039+
1040+
override string toString() { result = "'arguments' object of " + function.describe() }
1041+
1042+
override predicate hasLocationInfo(
1043+
string filepath, int startline, int startcolumn, int endline, int endcolumn
1044+
) {
1045+
function.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
1046+
}
1047+
1048+
override BasicBlock getBasicBlock() { result = function.getEntry().getBasicBlock() }
1049+
1050+
/**
1051+
* Gets the function whose `arguments` object is represented by this node.
1052+
*/
1053+
Function getFunction() { result = function }
1054+
1055+
override File getFile() { result = function.getFile() }
1056+
}
1057+
10321058
/**
10331059
* A data flow node representing the exceptions thrown by the callee of an invocation.
10341060
*/
@@ -1627,6 +1653,35 @@ module DataFlow {
16271653
exists(Function f | not f.isAsyncOrGenerator() |
16281654
DataFlow::functionReturnNode(succ, f) and pred = valueNode(f.getAReturnedExpr())
16291655
)
1656+
or
1657+
// from a reflective params node to a reference to the arguments object.
1658+
exists(DataFlow::ReflectiveParametersNode params, Function f | f = params.getFunction() |
1659+
succ = f.getArgumentsVariable().getAnAccess().flow() and
1660+
pred = params
1661+
)
1662+
}
1663+
1664+
/** A load step from a reflective parameter node to each parameter. */
1665+
private class ReflectiveParamsStep extends PreCallGraphStep {
1666+
override predicate loadStep(DataFlow::Node obj, DataFlow::Node element, string prop) {
1667+
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f, int i |
1668+
f.getFunction() = params.getFunction() and
1669+
obj = params and
1670+
prop = i + "" and
1671+
element = f.getParameter(i)
1672+
)
1673+
}
1674+
}
1675+
1676+
/** A taint step from the reflective parameters node to any parameter. */
1677+
private class ReflectiveParamsTaintStep extends TaintTracking::SharedTaintStep {
1678+
override predicate step(DataFlow::Node obj, DataFlow::Node element) {
1679+
exists(DataFlow::ReflectiveParametersNode params, DataFlow::FunctionNode f |
1680+
f.getFunction() = params.getFunction() and
1681+
obj = params and
1682+
element = f.getAParameter()
1683+
)
1684+
}
16301685
}
16311686

16321687
/**

javascript/ql/lib/semmle/javascript/dataflow/Sources.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ module SourceNode {
332332
or
333333
// Include return nodes because they model the implicit Promise creation in async functions.
334334
DataFlow::functionReturnNode(this, _)
335+
or
336+
this instanceof DataFlow::ReflectiveParametersNode
335337
}
336338
}
337339
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ newtype TNode =
3131
TExceptionalFunctionReturnNode(Function f) or
3232
TExceptionalInvocationReturnNode(InvokeExpr e) or
3333
TGlobalAccessPathRoot() or
34-
TTemplatePlaceholderTag(Templating::TemplatePlaceholderTag tag)
34+
TTemplatePlaceholderTag(Templating::TemplatePlaceholderTag tag) or
35+
TReflectiveParametersNode(Function f)

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,14 @@ private module CachedSteps {
224224
or
225225
arg = invk.(DataFlow::PropWrite).getRhs() and
226226
parm = DataFlow::parameterNode(f.getParameter(0))
227+
or
228+
calls(invk, f) and
229+
exists(MethodCallExpr apply |
230+
invk = DataFlow::reflectiveCallNode(apply) and
231+
apply.getMethodName() = "apply" and
232+
arg = apply.getArgument(1).flow()
233+
) and
234+
parm.(DataFlow::ReflectiveParametersNode).getFunction() = f
227235
)
228236
or
229237
exists(DataFlow::Node callback, int i, Parameter p, Function target |

javascript/ql/lib/semmle/javascript/security/dataflow/PrototypePollutingAssignmentCustomizations.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ module PrototypePollutingAssignment {
6868
/**
6969
* A parameter of an exported function, seen as a source prototype-polluting assignment.
7070
*/
71-
class ExternalInputSource extends Source, DataFlow::SourceNode {
71+
class ExternalInputSource extends Source {
7272
ExternalInputSource() { this = Exports::getALibraryInputParameter() }
7373

7474
override string describe() { result = "library input" }

javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeCodeConstructionCustomizations.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@ module UnsafeCodeConstruction {
2121
/**
2222
* A parameter of an exported function, seen as a source.
2323
*/
24-
class ExternalInputSource extends Source, DataFlow::ParameterNode {
24+
class ExternalInputSource extends Source {
2525
ExternalInputSource() {
2626
this = Exports::getALibraryInputParameter() and
2727
// permit parameters that clearly are intended to contain executable code.
28-
not this.getName() = "code"
28+
not this.(DataFlow::ParameterNode).getName() = "code"
2929
}
3030
}
3131

javascript/ql/lib/semmle/javascript/security/dataflow/UnsafeHtmlConstructionCustomizations.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ module UnsafeHtmlConstruction {
2222
/**
2323
* A parameter of an exported function, seen as a source for usnafe HTML constructed from input.
2424
*/
25-
class ExternalInputSource extends Source, DataFlow::ParameterNode {
25+
class ExternalInputSource extends Source {
2626
ExternalInputSource() {
2727
this = Exports::getALibraryInputParameter() and
2828
// An AMD-style module sometimes loads the jQuery library in a way which looks like library input.

0 commit comments

Comments
 (0)