Skip to content

Commit 0753c8a

Browse files
authored
Merge pull request #4247 from erik-krogh/CVE760-reexport
Approved by asgerf
2 parents ef703e7 + f7f82ff commit 0753c8a

39 files changed

+305
-68
lines changed

javascript/ql/src/NodeJS/MissingExports.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ predicate definedInModule(GlobalVariable v, NodeModule m) {
2020
)
2121
}
2222

23-
from NodeModule m, GlobalVariable f, InvokeExpr invk, ASTNode export, GlobalVarAccess acc
23+
from NodeModule m, GlobalVariable f, InvokeExpr invk, DataFlow::Node export, GlobalVarAccess acc
2424
where
25-
m.exports(f.getName(), export) and
25+
export = m.getAnExportedValue(f.getName()) and
2626
acc = f.getAnAccess() and
2727
invk.getCallee() = acc and
2828
invk.getTopLevel() = m and

javascript/ql/src/semmle/javascript/AMD.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,8 @@ class AmdModule extends Module {
295295
/** Gets the definition of this module. */
296296
AmdModuleDefinition getDefine() { amdModuleTopLevel(result, this) }
297297

298-
override predicate exports(string name, ASTNode export) {
299-
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
298+
override DataFlow::Node getAnExportedValue(string name) {
299+
exists(DataFlow::PropWrite pwn | result = pwn.getRhs() |
300300
pwn.getBase().analyze().getAValue() = getDefine().getAModuleExportsValue() and
301301
name = pwn.getPropertyName()
302302
)

javascript/ql/src/semmle/javascript/Closure.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,9 @@ module Closure {
165165
result = getScope().getVariable("exports")
166166
}
167167

168-
override predicate exports(string name, ASTNode export) {
168+
override DataFlow::Node getAnExportedValue(string name) {
169169
exists(DataFlow::PropWrite write, Expr base |
170-
write.getAstNode() = export and
170+
result = write.getRhs() and
171171
write.writes(base.flow(), name, _) and
172172
(
173173
base = getExportsVariable().getAReference()

javascript/ql/src/semmle/javascript/ES2015Modules.qll

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ class ES2015Module extends Module {
2727
/** Gets an export declaration in this module. */
2828
ExportDeclaration getAnExport() { result.getTopLevel() = this }
2929

30-
override predicate exports(string name, ASTNode export) {
31-
exists(ExportDeclaration ed | ed = getAnExport() and ed = export | ed.exportsAs(_, name))
30+
override DataFlow::Node getAnExportedValue(string name) {
31+
exists(ExportDeclaration ed | ed = getAnExport() and result = ed.getSourceNode(name))
3232
}
3333

3434
/** Holds if this module exports variable `v` under the name `name`. */
@@ -395,6 +395,13 @@ class ExportNamedDeclaration extends ExportDeclaration, @export_named_declaratio
395395
result = DataFlow::valueNode(d.getSource())
396396
)
397397
or
398+
exists(ObjectPattern obj | obj = getOperand().(DeclStmt).getADecl().getBindingPattern() |
399+
exists(DataFlow::PropRead read | read = result |
400+
read.getBase() = obj.flow() and
401+
name = read.getPropertyName()
402+
)
403+
)
404+
or
398405
exists(ExportSpecifier spec | spec = getASpecifier() and name = spec.getExportedName() |
399406
not exists(getImportedPath()) and result = DataFlow::valueNode(spec.getLocal())
400407
or

javascript/ql/src/semmle/javascript/Modules.qll

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ abstract class Module extends TopLevel {
2424
Module getAnImportedModule() { result = getAnImport().getImportedModule() }
2525

2626
/** Gets a symbol exported by this module. */
27-
string getAnExportedSymbol() { exports(result, _) }
27+
string getAnExportedSymbol() { exists(getAnExportedValue(result)) }
2828

2929
/**
30+
* DEPRECATED. Use `getAnExportedValue` instead.
31+
*
3032
* Holds if this module explicitly exports symbol `name` at the
3133
* program element `export`.
3234
*
@@ -36,9 +38,77 @@ abstract class Module extends TopLevel {
3638
* that are explicitly defined on the module object.
3739
*
3840
* Symbols defined in another module that are re-exported by
39-
* this module are not considered either.
41+
* this module are only sometimes considered.
42+
*/
43+
deprecated predicate exports(string name, ASTNode export) {
44+
this instanceof AmdModule and
45+
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
46+
pwn.getBase().analyze().getAValue() = this.(AmdModule).getDefine().getAModuleExportsValue() and
47+
name = pwn.getPropertyName()
48+
)
49+
or
50+
this instanceof Closure::ClosureModule and
51+
exists(DataFlow::PropWrite write, Expr base |
52+
write.getAstNode() = export and
53+
write.writes(base.flow(), name, _) and
54+
(
55+
base = this.(Closure::ClosureModule).getExportsVariable().getAReference()
56+
or
57+
base = this.(Closure::ClosureModule).getExportsVariable().getAnAssignedExpr()
58+
)
59+
)
60+
or
61+
this instanceof NodeModule and
62+
(
63+
// a property write whose base is `exports` or `module.exports`
64+
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
65+
pwn.getBase() = this.(NodeModule).getAModuleExportsNode() and
66+
name = pwn.getPropertyName()
67+
)
68+
or
69+
// a re-export using spread-operator. E.g. `const foo = require("./foo"); module.exports = {bar: bar, ...foo};`
70+
exists(ObjectExpr obj | obj = this.(NodeModule).getAModuleExportsNode().asExpr() |
71+
obj
72+
.getAProperty()
73+
.(SpreadProperty)
74+
.getInit()
75+
.(SpreadElement)
76+
.getOperand()
77+
.flow()
78+
.getALocalSource()
79+
.asExpr()
80+
.(Import)
81+
.getImportedModule()
82+
.exports(name, export)
83+
)
84+
or
85+
// an externs definition (where appropriate)
86+
exists(PropAccess pacc | export = pacc |
87+
pacc.getBase() = this.(NodeModule).getAModuleExportsNode().asExpr() and
88+
name = pacc.getPropertyName() and
89+
isExterns() and
90+
exists(pacc.getDocumentation())
91+
)
92+
)
93+
or
94+
this instanceof ES2015Module and
95+
exists(ExportDeclaration ed | ed = this.(ES2015Module).getAnExport() and ed = export |
96+
ed.exportsAs(_, name)
97+
)
98+
}
99+
100+
/**
101+
* Get a value that is explicitly exported from this module with under `name`.
102+
*
103+
* Note that in some module systems (notably CommonJS and AMD)
104+
* modules are arbitrary objects that export all their
105+
* properties. This predicate only considers properties
106+
* that are explicitly defined on the module object.
107+
*
108+
* Symbols defined in another module that are re-exported by
109+
* this module are only sometimes considered.
40110
*/
41-
abstract predicate exports(string name, ASTNode export);
111+
abstract DataFlow::Node getAnExportedValue(string name);
42112

43113
/**
44114
* Gets the root folder relative to which the given import path (which must

javascript/ql/src/semmle/javascript/NodeJS.qll

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,22 +42,64 @@ class NodeModule extends Module {
4242
)
4343
}
4444

45+
/**
46+
* Gets an expression that is an alias for `module.exports`.
47+
* For performance this predicate only computes relevant expressions.
48+
* So if using this predicate - consider expanding the list of relevant expressions.
49+
*/
50+
pragma[noinline]
51+
DataFlow::Node getAModuleExportsNode() {
52+
(
53+
// A bit of manual magic
54+
result = any(DataFlow::PropWrite w | exists(w.getPropertyName())).getBase()
55+
or
56+
result = DataFlow::valueNode(any(PropAccess p | exists(p.getPropertyName())).getBase())
57+
or
58+
result = DataFlow::valueNode(any(ObjectExpr obj))
59+
) and
60+
result.analyze().getAValue() = getAModuleExportsValue()
61+
}
62+
4563
/** Gets a symbol exported by this module. */
4664
override string getAnExportedSymbol() {
47-
result = super.getAnExportedSymbol() or
65+
result = super.getAnExportedSymbol()
66+
or
4867
result = getAnImplicitlyExportedSymbol()
68+
or
69+
// getters and the like.
70+
exists(DataFlow::PropWrite pwn |
71+
pwn.getBase() = this.getAModuleExportsNode() and
72+
result = pwn.getPropertyName()
73+
)
4974
}
5075

51-
override predicate exports(string name, ASTNode export) {
76+
override DataFlow::Node getAnExportedValue(string name) {
5277
// a property write whose base is `exports` or `module.exports`
53-
exists(DataFlow::PropWrite pwn | export = pwn.getAstNode() |
54-
pwn.getBase().analyze().getAValue() = getAModuleExportsValue() and
78+
exists(DataFlow::PropWrite pwn | result = pwn.getRhs() |
79+
pwn.getBase() = getAModuleExportsNode() and
5580
name = pwn.getPropertyName()
5681
)
5782
or
83+
// a re-export using spread-operator. E.g. `const foo = require("./foo"); module.exports = {bar: bar, ...foo};`
84+
exists(ObjectExpr obj | obj = getAModuleExportsNode().asExpr() |
85+
result =
86+
obj
87+
.getAProperty()
88+
.(SpreadProperty)
89+
.getInit()
90+
.(SpreadElement)
91+
.getOperand()
92+
.flow()
93+
.getALocalSource()
94+
.asExpr()
95+
.(Import)
96+
.getImportedModule()
97+
.getAnExportedValue(name)
98+
)
99+
or
58100
// an externs definition (where appropriate)
59-
exists(PropAccess pacc | export = pacc |
60-
pacc.getBase().analyze().getAValue() = getAModuleExportsValue() and
101+
exists(PropAccess pacc | result = DataFlow::valueNode(pacc) |
102+
pacc.getBase() = getAModuleExportsNode().asExpr() and
61103
name = pacc.getPropertyName() and
62104
isExterns() and
63105
exists(pacc.getDocumentation())

javascript/ql/src/semmle/javascript/PackageExports.qll

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ DataFlow::Node getAValueExportedBy(PackageJSON packageJSON) {
4040
exists(DataFlow::SourceNode callee |
4141
callee = getAValueExportedBy(packageJSON).(DataFlow::NewNode).getCalleeNode().getALocalSource()
4242
|
43-
result = callee.getAPropertyRead("prototype").getAPropertyWrite()
43+
result = callee.getAPropertyRead("prototype").getAPropertyWrite().getRhs()
4444
or
4545
result = callee.(DataFlow::ClassNode).getAnInstanceMethod()
4646
)
@@ -68,10 +68,5 @@ DataFlow::Node getAValueExportedBy(PackageJSON packageJSON) {
6868
private DataFlow::Node getAnExportFromModule(Module mod) {
6969
result.analyze().getAValue() = mod.(NodeModule).getAModuleExportsValue()
7070
or
71-
exists(ASTNode export | result.getEnclosingExpr() = export | mod.exports(_, export))
72-
or
73-
exists(ExportDeclaration export |
74-
result = export.getSourceNode(_) and
75-
mod = export.getTopLevel()
76-
)
71+
result = mod.getAnExportedValue(_)
7772
}

javascript/ql/src/semmle/javascript/dataflow/Portals.qll

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,9 @@ private module NpmPackagePortal {
233233
apw.writes(m.(AnalyzedModule).getModuleObject(), "exports", exp)
234234
)
235235
or
236-
m.(ES2015Module).exports("default", exp.(DataFlow::ValueNode).getAstNode())
236+
exists(DataFlow::PropWrite export | exp = export |
237+
export.getRhs() = m.(ES2015Module).getAnExportedValue("default")
238+
)
237239
)
238240
}
239241
}

javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
private import javascript
66
private import semmle.javascript.dataflow.internal.StepSummary
7+
private import semmle.javascript.dataflow.internal.PreCallGraphStep
78

89
cached
910
module CallGraph {
@@ -48,6 +49,10 @@ module CallGraph {
4849
t.start() and
4950
AccessPath::step(function, result)
5051
or
52+
t.start() and
53+
imprecision = 0 and
54+
PreCallGraphStep::step(any(DataFlow::Node n | function.flowsTo(n)), result)
55+
or
5156
imprecision = 0 and
5257
exists(DataFlow::ClassNode cls |
5358
exists(string name |

javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,20 @@ module NodeJSLib {
643643
}
644644
}
645645

646+
private import semmle.javascript.PackageExports as Exports
647+
648+
/**
649+
* A direct step from an named export to a property-read reading the exported value.
650+
*/
651+
private class ExportsStep extends PreCallGraphStep {
652+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
653+
exists(Import imp, string name |
654+
succ = DataFlow::valueNode(imp).(DataFlow::SourceNode).getAPropertyRead(name) and
655+
pred = imp.getImportedModule().getAnExportedValue(name)
656+
)
657+
}
658+
}
659+
646660
/**
647661
* A call to a method from module `child_process`.
648662
*/

0 commit comments

Comments
 (0)