-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathNodeJS.qll
More file actions
374 lines (342 loc) · 12.3 KB
/
NodeJS.qll
File metadata and controls
374 lines (342 loc) · 12.3 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
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
/** Provides classes for working with Node.js modules. */
import javascript
private import NodeModuleResolutionImpl
private import semmle.javascript.DynamicPropertyAccess as DynamicPropertyAccess
private import semmle.javascript.internal.CachedStages
private import semmle.javascript.dataflow.internal.DataFlowNode
/**
* A Node.js module.
*
* Example:
*
* ```
* const fs = require('fs');
* for (var i=2;i<process.argv.length; ++i)
* process.stdout.write(fs.readFileSync(process.argv[i], 'utf8'));
* ```
*/
class NodeModule extends Module {
NodeModule() {
is_module(this) and
is_nodejs(this)
}
/** Gets the `module` variable of this module. */
Variable getModuleVariable() { result = this.getScope().getVariable("module") }
/** Gets the `exports` variable of this module. */
Variable getExportsVariable() { result = this.getScope().getVariable("exports") }
/** Gets the scope induced by this module. */
override ModuleScope getScope() { result.getScopeElement() = this }
/**
* Gets an abstract value representing one or more values that may flow
* into this module's `module.exports` property.
*/
pragma[noinline]
DefiniteAbstractValue getAModuleExportsValue() {
result = this.getAModuleExportsProperty().getAValue()
}
pragma[noinline]
private AbstractProperty getAModuleExportsProperty() {
result.getBase().(AbstractModuleObject).getModule() = this and
result.getPropertyName() = "exports"
}
/**
* Gets an expression that is an alias for `module.exports`.
* For performance this predicate only computes relevant expressions (in `getAModuleExportsCandidate`).
* So if using this predicate - consider expanding the list of relevant expressions.
*/
DataFlow::AnalyzedNode getAModuleExportsNode() {
result = getAModuleExportsCandidate() and
result.getAValue() = this.getAModuleExportsValue()
}
override DataFlow::Node getAnExportedValue(string name) {
// a property write whose base is `exports` or `module.exports`
exists(DataFlow::PropWrite pwn | result = pwn.getRhs() |
pwn.getBase() = this.getAModuleExportsNode() and
name = pwn.getPropertyName()
)
or
// a re-export using spread-operator. E.g. `const foo = require("./foo"); module.exports = {bar: bar, ...foo};`
exists(ObjectExpr obj | obj = this.getAModuleExportsNode().asExpr() |
result =
obj.getAProperty()
.(SpreadProperty)
.getInit()
.(SpreadElement)
.getOperand()
.flow()
.getALocalSource()
.asExpr()
.(Import)
.getImportedModule()
.getAnExportedValue(name)
)
or
// var imp = require('./imp');
// for (var name in imp){
// module.exports[name] = imp[name];
// }
exists(DynamicPropertyAccess::EnumeratedPropName read, Import imp, DataFlow::PropWrite write |
read.getSourceObject().getALocalSource().asExpr() = imp and
getASourceProp(read) = write.getRhs() and
write.getBase() = this.getAModuleExportsNode() and
write.getPropertyNameExpr().flow().getImmediatePredecessor*() = read and
result = imp.getImportedModule().getAnExportedValue(name)
)
or
// an externs definition (where appropriate)
exists(PropAccess pacc | result = DataFlow::valueNode(pacc) |
pacc.getBase() = this.getAModuleExportsNode().asExpr() and
name = pacc.getPropertyName() and
this.isExterns() and
exists(pacc.getDocumentation())
)
}
override DataFlow::Node getABulkExportedNode() {
Stages::Imports::ref() and
exists(DataFlow::PropWrite write |
write.getBase().asExpr() = this.getModuleVariable().getAnAccess() and
write.getPropertyName() = "exports" and
result = write.getRhs()
)
}
deprecated override predicate searchRoot(PathExpr path, Folder searchRoot, int priority) {
path.getEnclosingModule() = this and
exists(string pathval | pathval = path.getValue() |
// paths starting with `./` or `../` are resolved relative to the importing
// module's folder
pathval.regexpMatch("\\.\\.?(/.*)?") and
(searchRoot = this.getFile().getParentContainer() and priority = 0)
or
// paths starting with `/` are resolved relative to the file system root
pathval.matches("/%") and
(searchRoot.getBaseName() = "" and priority = 0)
or
// paths that do not start with `./`, `../` or `/` are resolved relative
// to `node_modules` folders
not pathval.regexpMatch("\\.\\.?(/.*)?|/.*") and
findNodeModulesFolder(this.getFile().getParentContainer(), searchRoot, priority)
)
}
}
// An copy of `DynamicPropertyAccess::EnumeratedPropName::getASourceProp` that doesn't use the callgraph.
// This avoids making the module-imports recursive with the callgraph.
private DataFlow::SourceNode getASourceProp(DynamicPropertyAccess::EnumeratedPropName prop) {
exists(DataFlow::Node base, DataFlow::Node key |
exists(DynamicPropertyAccess::DynamicPropRead read |
not read.hasDominatingAssignment() and
base = read.getBase() and
key = read.getPropertyNameNode() and
result = read
) and
prop.getASourceObjectRef().flowsTo(base) and
key.getImmediatePredecessor*() = prop
)
}
/**
* Gets an expression that syntactically could be a alias for `module.exports`.
* This predicate exists to reduce the size of `getAModuleExportsNode`,
* while keeping all the tuples that could be relevant in later computations.
*/
pragma[noinline]
private DataFlow::Node getAModuleExportsCandidate() {
// A bit of manual magic
result = any(DataFlow::PropWrite w).getBase()
or
result = DataFlow::valueNode(any(PropAccess p | exists(p.getPropertyName())).getBase())
or
result = DataFlow::valueNode(any(ObjectExpr obj))
}
/**
* Holds if `nodeModules` is a folder of the form `<prefix>/node_modules`, where
* `<prefix>` is a (not necessarily proper) prefix of `f` and does not end in `/node_modules`,
* and `distance` is the number of path elements of `f` that are missing from `<prefix>`.
*
* This predicate implements the `NODE_MODULES_PATHS` procedure from the
* [specification of `require.resolve`](https://nodejs.org/api/modules.html#modules_all_together).
*
* For example, if `f` is `/a/node_modules/b`, we get the following results:
*
* <table border="1">
* <tr><th><code>nodeModules</code></th><th><code>distance</code></th></tr>
* <tr><td><code>/a/node_modules/b/node_modules</code></td><td>0</td></tr>
* <tr><td><code>/a/node_modules</code></td><td>2</td></tr>
* <tr><td><code>/node_modules</code></td><td>3</td></tr>
* </table>
*/
predicate findNodeModulesFolder(Folder f, Folder nodeModules, int distance) {
nodeModules = f.getFolder("node_modules") and
not f.getBaseName() = "node_modules" and
distance = 0
or
findNodeModulesFolder(f.getParentContainer(), nodeModules, distance - 1)
}
/**
* A Node.js `require` variable.
*/
private class RequireVariable extends Variable {
RequireVariable() {
this = any(ModuleScope m).getVariable("require")
or
// cover cases where we failed to detect Node.js code
this.(GlobalVariable).getName() = "require"
or
// track through assignments to other variables
this.getAnAssignedExpr().(VarAccess).getVariable() instanceof RequireVariable
}
}
private predicate isModuleModule(EarlyStageNode nd) {
exists(ImportDeclaration imp | imp.getRawImportPath() = "module" |
nd = TDestructuredModuleImportNode(imp)
or
nd = TValueNode(imp.getASpecifier().(ImportNamespaceSpecifier))
)
or
exists(EarlyStageNode other |
isModuleModule(other) and
DataFlow::localFlowStep(other, nd)
)
}
private predicate isCreateRequire(EarlyStageNode nd) {
exists(PropAccess prop |
isModuleModule(TValueNode(prop.getBase())) and
prop.getPropertyName() = "createRequire" and
nd = TValueNode(prop)
)
or
exists(PropertyPattern prop |
isModuleModule(TValueNode(prop.getObjectPattern())) and
prop.getName() = "createRequire" and
nd = TValueNode(prop.getValuePattern())
)
or
exists(ImportDeclaration decl, NamedImportSpecifier spec |
decl.getRawImportPath() = "module" and
spec = decl.getASpecifier() and
spec.getImportedName() = "createRequire" and
nd = TValueNode(spec)
)
or
exists(EarlyStageNode other |
isCreateRequire(other) and
DataFlow::localFlowStep(other, nd)
)
}
/**
* Holds if `nd` may refer to `require`, either directly or modulo local data flow.
*/
cached
private predicate isRequire(EarlyStageNode nd) {
exists(VarAccess access |
access = any(RequireVariable v).getAnAccess() and
nd = TValueNode(access) and
// `mjs` files explicitly disallow `require`
not access.getFile().getExtension() = "mjs"
)
or
exists(EarlyStageNode other |
isRequire(other) and
DataFlow::localFlowStep(other, nd)
)
or
// `import { createRequire } from 'module';`.
// specialized to ES2015 modules to avoid recursion in the `DataFlow::moduleImport()` predicate and to avoid
// negative recursion between `Import.getImportedModuleNode()` and `Import.getImportedModule()`, and
// to avoid depending on `SourceNode` as this would make `SourceNode::Range` recursive.
exists(CallExpr call |
isCreateRequire(TValueNode(call.getCallee())) and
nd = TValueNode(call)
)
or
// `$.require('underscore');`.
// NPM as supported in [XSJS files](https://www.npmjs.com/package/@sap/async-xsjs#npm-packages-support).
exists(MethodCallExpr require |
require.getFile().getExtension() = ["xsjs", "xsjslib"] and
require.getCalleeName() = "require" and
require.getReceiver().(GlobalVarAccess).getName() = "$" and
nd = TValueNode(require.getCallee())
)
}
/**
* A `require` import.
*
* Example:
*
* ```
* require('fs')
* ```
*/
class Require extends CallExpr, Import {
Require() { isRequire(TValueNode(this.getCallee())) }
override Expr getImportedPathExpr() { result = this.getArgument(0) }
override Module getEnclosingModule() { this = result.getAnImport() }
override DataFlow::Node getImportedModuleNode() { result = DataFlow::valueNode(this) }
}
/** An argument to `require` or `require.resolve`, considered as a path expression. */
deprecated private class RequirePath extends PathExprCandidate {
RequirePath() {
this = any(Require req).getArgument(0)
or
exists(MethodCallExpr reqres |
isRequire(TValueNode(reqres.getReceiver())) and
reqres.getMethodName() = "resolve" and
this = reqres.getArgument(0)
)
}
}
/** A constant path element appearing in a call to `require` or `require.resolve`. */
deprecated private class ConstantRequirePathElement extends PathExpr, ConstantString {
ConstantRequirePathElement() { this = any(RequirePath rp).getAPart() }
override string getValue() { result = this.getStringValue() }
}
/** A `__dirname` path expression. */
deprecated private class DirNamePath extends PathExpr, VarAccess {
DirNamePath() {
this.getName() = "__dirname" and
this.getVariable().getScope() instanceof ModuleScope
}
override string getValue() { result = this.getFile().getParentContainer().getAbsolutePath() }
}
/** A `__filename` path expression. */
deprecated private class FileNamePath extends PathExpr, VarAccess {
FileNamePath() {
this.getName() = "__filename" and
this.getVariable().getScope() instanceof ModuleScope
}
override string getValue() { result = this.getFile().getAbsolutePath() }
}
/**
* A path expression of the form `path.join(p, "...")` where
* `p` is also a path expression.
*/
deprecated private class JoinedPath extends PathExpr, @call_expr {
JoinedPath() {
exists(MethodCallExpr call | call = this |
call.getReceiver().(VarAccess).getName() = "path" and
call.getMethodName() = "join" and
call.getNumArgument() = 2 and
call.getArgument(0) instanceof PathExpr and
call.getArgument(1) instanceof ConstantString
)
}
override string getValue() {
exists(CallExpr call, PathExpr left, ConstantString right |
call = this and
left = call.getArgument(0) and
right = call.getArgument(1)
|
result = left.getValue() + "/" + right.getStringValue()
)
}
}
/**
* A reference to the special `module` variable.
*
* Example:
*
* ```
* module
* ```
*/
class ModuleAccess extends VarAccess {
ModuleAccess() { exists(ModuleScope ms | this = ms.getVariable("module").getAnAccess()) }
}