Skip to content

Commit e1aff15

Browse files
authored
Merge pull request #18125 from asgerf/jss/summary-type-tracker
JS: Derive type-tracking steps from flow summaries
2 parents 66d6bda + 054558d commit e1aff15

20 files changed

+289
-65
lines changed

javascript/ql/lib/qlpack.yml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies:
1212
codeql/ssa: ${workspace}
1313
codeql/threat-models: ${workspace}
1414
codeql/tutorial: ${workspace}
15+
codeql/typetracking: ${workspace}
1516
codeql/util: ${workspace}
1617
codeql/xml: ${workspace}
1718
codeql/yaml: ${workspace}

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,18 @@ abstract class SummarizedCallable extends LibraryCallable, Impl::Public::Summari
1111
bindingset[this]
1212
SummarizedCallable() { any() }
1313

14-
// TODO: rename 'propagatesFlowExt' and/or override 'propagatesFlow' directly
1514
/**
1615
* Holds if data may flow from `input` to `output` through this callable.
1716
*
1817
* `preservesValue` indicates whether this is a value-preserving step or a taint-step.
1918
*/
2019
pragma[nomagic]
21-
predicate propagatesFlowExt(string input, string output, boolean preservesValue) { none() }
20+
predicate propagatesFlow(string input, string output, boolean preservesValue) { none() }
2221

2322
override predicate propagatesFlow(
2423
string input, string output, boolean preservesValue, string model
2524
) {
26-
this.propagatesFlowExt(input, output, preservesValue) and model = this
25+
this.propagatesFlow(input, output, preservesValue) and model = this
2726
}
2827

2928
/**

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

+25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import javascript
22
private import semmle.javascript.dataflow.TypeTracking
33
private import semmle.javascript.internal.CachedStages
4+
private import semmle.javascript.dataflow.internal.Contents as Contents
5+
private import sharedlib.SummaryTypeTracker as SummaryTypeTracker
46
private import FlowSteps
57

68
cached
@@ -29,6 +31,8 @@ private module Cached {
2931
SharedTypeTrackingStep::loadStoreStep(_, _, _, this)
3032
or
3133
this = DataFlow::PseudoProperties::arrayLikeElement()
34+
or
35+
this instanceof Contents::Private::PropertyName
3236
}
3337
}
3438

@@ -46,6 +50,12 @@ private module Cached {
4650
LoadStoreStep(PropertyName fromProp, PropertyName toProp) {
4751
SharedTypeTrackingStep::loadStoreStep(_, _, fromProp, toProp)
4852
or
53+
exists(DataFlow::ContentSet loadContent, DataFlow::ContentSet storeContent |
54+
SummaryTypeTracker::basicLoadStoreStep(_, _, loadContent, storeContent) and
55+
fromProp = loadContent.asPropertyName() and
56+
toProp = storeContent.asPropertyName()
57+
)
58+
or
4959
summarizedLoadStoreStep(_, _, fromProp, toProp)
5060
} or
5161
WithoutPropStep(PropertySet props) { SharedTypeTrackingStep::withoutPropStep(_, _, props) }
@@ -205,6 +215,21 @@ private module Cached {
205215
succ = getACallbackSource(parameter).getParameter(i) and
206216
summary = ReturnStep()
207217
)
218+
or
219+
SummaryTypeTracker::levelStepNoCall(pred, succ) and summary = LevelStep()
220+
or
221+
exists(DataFlow::ContentSet content |
222+
SummaryTypeTracker::basicLoadStep(pred, succ, content) and
223+
summary = LoadStep(content.asPropertyName())
224+
or
225+
SummaryTypeTracker::basicStoreStep(pred, succ, content) and
226+
summary = StoreStep(content.asPropertyName())
227+
)
228+
or
229+
exists(DataFlow::ContentSet loadContent, DataFlow::ContentSet storeContent |
230+
SummaryTypeTracker::basicLoadStoreStep(pred, succ, loadContent, storeContent) and
231+
summary = LoadStoreStep(loadContent.asPropertyName(), storeContent.asPropertyName())
232+
)
208233
}
209234
}
210235

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
private import semmle.javascript.Locations
2+
private import codeql.typetracking.internal.SummaryTypeTracker
3+
private import semmle.javascript.dataflow.internal.DataFlowPrivate as DataFlowPrivate
4+
private import semmle.javascript.dataflow.FlowSummary as FlowSummary
5+
private import FlowSummaryImpl as FlowSummaryImpl
6+
private import DataFlowArg
7+
8+
private module SummaryFlowConfig implements Input {
9+
import JSDataFlow
10+
import FlowSummaryImpl::Public
11+
import FlowSummaryImpl::Private
12+
import FlowSummaryImpl::Private::SummaryComponent
13+
14+
class Content = DataFlow::ContentSet;
15+
16+
class ContentFilter extends Unit {
17+
ContentFilter() { none() }
18+
}
19+
20+
ContentFilter getFilterFromWithoutContentStep(Content content) { none() }
21+
22+
ContentFilter getFilterFromWithContentStep(Content content) { none() }
23+
24+
predicate singleton = SummaryComponentStack::singleton/1;
25+
26+
predicate push = SummaryComponentStack::push/2;
27+
28+
SummaryComponent return() {
29+
result = SummaryComponent::return(DataFlowPrivate::MkNormalReturnKind())
30+
}
31+
32+
Node argumentOf(Node call, SummaryComponent arg, boolean isPostUpdate) {
33+
// Note: we cannot rely on DataFlowPrivate::DataFlowCall here because that depends on the call graph.
34+
exists(ArgumentPosition apos, ParameterPosition ppos, Node argNode |
35+
arg = argument(ppos) and
36+
parameterMatch(ppos, apos) and
37+
(
38+
argNode = call.(DataFlow::InvokeNode).getArgument(apos.asPositional())
39+
or
40+
apos.isThis() and
41+
argNode = call.(DataFlow::CallNode).getReceiver()
42+
)
43+
|
44+
isPostUpdate = true and result = argNode.getPostUpdateNode()
45+
or
46+
isPostUpdate = false and result = argNode
47+
)
48+
}
49+
50+
Node parameterOf(Node callable, SummaryComponent param) {
51+
exists(ArgumentPosition apos, ParameterPosition ppos, DataFlow::FunctionNode function |
52+
param = parameter(apos) and
53+
parameterMatch(ppos, apos) and
54+
callable = function
55+
|
56+
result = function.getParameter(ppos.asPositional())
57+
or
58+
ppos.isThis() and
59+
result = function.getReceiver()
60+
)
61+
}
62+
63+
Node returnOf(Node callable, SummaryComponent return) {
64+
return = return() and
65+
result = callable.(DataFlow::FunctionNode).getReturnNode()
66+
}
67+
68+
class SummarizedCallable instanceof SummarizedCallableImpl {
69+
predicate propagatesFlow(
70+
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
71+
) {
72+
super.propagatesFlow(input, output, preservesValue, _)
73+
}
74+
75+
string toString() { result = super.toString() }
76+
}
77+
78+
Node callTo(SummarizedCallable callable) {
79+
result = callable.(FlowSummary::SummarizedCallable).getACallSimple()
80+
}
81+
}
82+
83+
import SummaryFlow<SummaryFlowConfig>

javascript/ql/lib/semmle/javascript/internal/flow_summaries/AmbiguousCoreMethods.qll

+7-7
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class At extends SummarizedCallable {
3131

3232
override InstanceCall getACallSimple() { result.getMethodName() = "at" }
3333

34-
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
34+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
3535
preservesValue = true and
3636
input = "Argument[this].ArrayElement" and
3737
output = "ReturnValue"
@@ -45,7 +45,7 @@ class Concat extends SummarizedCallable {
4545

4646
override InstanceCall getACallSimple() { result.getMethodName() = "concat" }
4747

48-
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
48+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
4949
preservesValue = true and
5050
input = "Argument[this,0..].ArrayElement" and
5151
output = "ReturnValue.ArrayElement"
@@ -61,7 +61,7 @@ class Slice extends SummarizedCallable {
6161

6262
override InstanceCall getACallSimple() { result.getMethodName() = "slice" }
6363

64-
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
64+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
6565
preservesValue = true and
6666
input = "Argument[this].ArrayElement" and
6767
output = "ReturnValue.ArrayElement"
@@ -80,7 +80,7 @@ class Entries extends SummarizedCallable {
8080
result.getNumArgument() = 0
8181
}
8282

83-
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
83+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
8484
preservesValue = true and
8585
(
8686
input = "Argument[this]." + ["MapKey", "SetElement"] and
@@ -97,7 +97,7 @@ class ForEach extends SummarizedCallable {
9797

9898
override InstanceCall getACallSimple() { result.getMethodName() = "forEach" }
9999

100-
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
100+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
101101
preservesValue = true and
102102
/*
103103
* array.forEach(callbackfn, thisArg)
@@ -128,7 +128,7 @@ class Keys extends SummarizedCallable {
128128
result.getNumArgument() = 0
129129
}
130130

131-
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
131+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
132132
preservesValue = true and
133133
input = "Argument[this]." + ["MapKey", "SetElement"] and
134134
output = "ReturnValue.IteratorElement"
@@ -143,7 +143,7 @@ class Values extends SummarizedCallable {
143143
result.getNumArgument() = 0
144144
}
145145

146-
override predicate propagatesFlowExt(string input, string output, boolean preservesValue) {
146+
override predicate propagatesFlow(string input, string output, boolean preservesValue) {
147147
preservesValue = true and
148148
input = "Argument[this]." + ["ArrayElement", "SetElement", "MapValue"] and
149149
output = "ReturnValue.IteratorElement"

0 commit comments

Comments
 (0)