-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathModelsAsData.qll
More file actions
295 lines (252 loc) · 9.8 KB
/
ModelsAsData.qll
File metadata and controls
295 lines (252 loc) · 9.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
/**
* Provides classes for contributing a model, or using the interpreted results
* of a model represented as data.
*
* - Use the `ModelInput` module to contribute new models.
* - Use the `ModelOutput` module to access the model results in terms of API nodes.
*
* The package name refers to an NPM package name or a path within a package name such as `lodash/extend`.
* The string `global` refers to the global object (whether it came from the `global` package or not).
*
* A `(package, type)` tuple may refer to the exported type named `type` from the NPM package `package`.
* For example, `(express, Request)` would match a parameter below due to the type annotation:
* ```ts
* import * as express from 'express';
* export function handler(req: express.Request) { ... }
* ```
*/
private import javascript
private import internal.ApiGraphModels as Shared
private import internal.ApiGraphModelsSpecific as Specific
private import semmle.javascript.dataflow.internal.FlowSummaryPrivate
private import semmle.javascript.endpoints.EndpointNaming as EndpointNaming
private import semmle.javascript.dataflow.AdditionalFlowSteps
private import semmle.javascript.dataflow.AdditionalTaintSteps
import Shared::ModelInput as ModelInput
import Shared::ModelOutput as ModelOutput
/**
* A remote flow source originating from a MaD source row.
*/
private class RemoteFlowSourceFromMaD extends RemoteFlowSource {
RemoteFlowSourceFromMaD() { ModelOutput::sourceNode(this, "remote") }
override string getSourceType() { result = "Remote flow" }
}
private class ClientSideRemoteFlowSourceFromMaD extends ClientSideRemoteFlowSource {
private ClientSideRemoteFlowKind kind;
ClientSideRemoteFlowSourceFromMaD() { ModelOutput::sourceNode(this, kind) }
override ClientSideRemoteFlowKind getKind() { result = kind }
override string getSourceType() {
result = "Source node (" + this.getThreatModel() + ") [from data-extension]"
}
}
/**
* A threat-model flow source originating from a data extension.
*/
private class ThreatModelSourceFromDataExtension extends ThreatModelSource::Range {
ThreatModelSourceFromDataExtension() { ModelOutput::sourceNode(this, _) }
override string getThreatModel() { ModelOutput::sourceNode(this, result) }
override string getSourceType() {
result = "Source node (" + this.getThreatModel() + ") [from data-extension]"
}
}
overlay[local?]
private class SummarizedCallableFromModel extends DataFlow::SummarizedCallable::Range {
string type;
string path;
SummarizedCallableFromModel() {
ModelOutput::relevantSummaryModel(type, path, _, _, _, _) and
this = type + ";" + path
}
overlay[global]
override DataFlow::InvokeNode getACall() { ModelOutput::resolvedSummaryBase(type, path, result) }
override predicate propagatesFlow(
string input, string output, boolean preservesValue, DataFlow::Provenance provenance,
boolean isExact, string model
) {
exists(string kind |
ModelOutput::relevantSummaryModel(type, path, input, output, kind, model) and
provenance = "manual" and
isExact = true
|
kind = "value" and
preservesValue = true
or
kind = "taint" and
preservesValue = false
)
}
predicate hasTypeAndPath(string type_, string path_) { type = type_ and path = path_ }
predicate isUnsupportedByFlowSummaries() { unsupportedCallable(this) }
}
private predicate shouldInduceStepsFromSummary(string type, string path) {
exists(SummarizedCallableFromModel callable |
callable.isUnsupportedByFlowSummaries() and
callable.hasTypeAndPath(type, path)
)
}
/**
* Holds if `path` is an input or output spec for a summary with the given `base` node.
*/
pragma[nomagic]
private predicate relevantInputOutputPath(API::InvokeNode base, AccessPath inputOrOutput) {
exists(string type, string input, string output, string path |
ModelOutput::resolvedSummaryBase(type, path, base) and
ModelOutput::relevantSummaryModel(type, path, input, output, _, _) and
inputOrOutput = [input, output]
)
}
/**
* Gets the API node for the first `n` tokens of the given input/output path, evaluated relative to `baseNode`.
*/
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path, int n) {
relevantInputOutputPath(baseNode, path) and
(
n = 1 and
result = Shared::getSuccessorFromInvoke(baseNode, path.getToken(0))
or
result =
Shared::getSuccessorFromNode(getNodeFromInputOutputPath(baseNode, path, n - 1),
path.getToken(n - 1))
)
}
/**
* Gets the API node for the given input/output path, evaluated relative to `baseNode`.
*/
private API::Node getNodeFromInputOutputPath(API::InvokeNode baseNode, AccessPath path) {
result = getNodeFromInputOutputPath(baseNode, path, path.getNumToken())
}
private predicate summaryStep(API::Node pred, API::Node succ, string kind, boolean shouldInduceSteps) {
exists(string type, string path, API::InvokeNode base, AccessPath input, AccessPath output |
ModelOutput::relevantSummaryModel(type, path, input, output, kind, _) and
ModelOutput::resolvedSummaryBase(type, path, base) and
pred = getNodeFromInputOutputPath(base, input) and
succ = getNodeFromInputOutputPath(base, output) and
if shouldInduceStepsFromSummary(type, path)
then shouldInduceSteps = true
else shouldInduceSteps = false
)
}
/**
* Like `ModelOutput::summaryStep` but with API nodes mapped to data-flow nodes.
*/
private predicate summaryStepNodes(
DataFlow::Node pred, DataFlow::Node succ, string kind, boolean shouldInduceSteps
) {
exists(API::Node predNode, API::Node succNode |
summaryStep(predNode, succNode, kind, shouldInduceSteps) and
pred = predNode.asSink() and
succ = succNode.asSource()
)
}
/** Data flow steps induced by summary models of kind `value`. */
private class DataFlowStepFromSummary extends DataFlow::SharedFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
summaryStepNodes(pred, succ, "value", true)
}
}
private class LegacyDataFlowStepFromSummary extends LegacyFlowStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
summaryStepNodes(pred, succ, "value", false)
}
}
/** Taint steps induced by summary models of kind `taint`. */
private class TaintStepFromSummary extends TaintTracking::SharedTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
summaryStepNodes(pred, succ, "taint", true)
}
}
private class LegacyTaintStepFromSummary extends LegacyTaintStep {
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
summaryStepNodes(pred, succ, "taint", false)
}
}
/**
* Specifies which parts of the API graph to export in `ModelExport`.
*/
signature module ModelExportSig {
/**
* Holds if the exported model should contain `node`, if it is publicly accessible.
*
* This ensures that all ways to access `node` will be exported in type models.
*/
predicate shouldContain(API::Node node);
/**
* Holds if `node` must be named if it is part of the exported graph.
*/
default predicate mustBeNamed(API::Node node) { none() }
/**
* Holds if the exported model should preserve all paths leading to an instance of `type`,
* including partial ones. It does not need to be closed transitively, `ModelExport` will
* extend this to include type models from which `type` can be derived.
*/
default predicate shouldContainType(string type) { none() }
}
/**
* Module for exporting type models for a given set of nodes in the API graph.
*/
module ModelExport<ModelExportSig S> {
private import codeql.mad.dynamic.GraphExport
private import internal.ApiGraphModelsExport
private module GraphExportConfig implements GraphExportSig<Location, API::Node> {
predicate edge = Specific::apiGraphHasEdge/3;
predicate shouldContain = S::shouldContain/1;
predicate shouldNotContain(API::Node node) {
EndpointNaming::isPrivateLike(node)
or
node instanceof API::Use
}
predicate mustBeNamed(API::Node node) {
node.getAValueReachingSink() instanceof DataFlow::ClassNode
or
node = API::Internal::getClassInstance(_)
or
S::mustBeNamed(node)
}
predicate exposedName(API::Node node, string type, string path) {
exists(string moduleName |
node = API::moduleExport(moduleName) and
path = "" and
type = "(" + moduleName + ")"
)
}
predicate suggestedName(API::Node node, string type) {
exists(string package, string name |
(
EndpointNaming::sinkHasPrimaryName(node, package, name) and
not EndpointNaming::aliasDefinition(_, _, _, _, node)
or
EndpointNaming::aliasDefinition(_, _, package, name, node)
) and
type = EndpointNaming::renderName(package, name)
)
}
bindingset[host]
predicate hasTypeSummary(API::Node host, string path) {
exists(string methodName |
functionReturnsReceiver(host.getMember(methodName).getAValueReachingSink()) and
path = "Member[" + methodName + "].ReturnValue"
)
}
pragma[nomagic]
private predicate functionReturnsReceiver(DataFlow::FunctionNode func) {
getAReceiverRef(func).flowsTo(func.getReturnNode())
}
pragma[nomagic]
private DataFlow::MethodCallNode getAReceiverCall(DataFlow::FunctionNode func) {
result = getAReceiverRef(func).getAMethodCall()
}
pragma[nomagic]
private predicate callReturnsReceiver(DataFlow::MethodCallNode call) {
functionReturnsReceiver(call.getACallee().flow())
}
pragma[nomagic]
private DataFlow::SourceNode getAReceiverRef(DataFlow::FunctionNode func) {
result = func.getReceiver()
or
result = getAReceiverCall(func) and
callReturnsReceiver(result)
}
}
private module ExportedGraph = TypeGraphExport<GraphExportConfig, S::shouldContainType/1>;
import ExportedGraph
}