Skip to content

Commit 6d55d1f

Browse files
authored
Merge pull request #1707 from asger-semmle/canonical-name-call-graph
Approved by xiemaisi
2 parents 742c970 + 9533ca0 commit 6d55d1f

File tree

20 files changed

+205
-87
lines changed

20 files changed

+205
-87
lines changed

change-notes/1.23/analysis-javascript.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* Support for the following frameworks and libraries has been improved:
66
- [firebase](https://www.npmjs.com/package/firebase)
77

8+
* The call graph has been improved to resolve method calls in more cases. This may produce more security alerts.
9+
810
## New queries
911

1012
| **Query** | **Tags** | **Purpose** |

javascript/externs/es/es5.js

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -236,20 +236,13 @@ Date.prototype.toISOString = function() {};
236236
Date.prototype.toJSON = function(opt_ignoredKey) {};
237237

238238

239-
/**
240-
* A fake type to model the JSON object.
241-
* @constructor
242-
*/
243-
function JSONType() {}
244-
245-
246239
/**
247240
* @param {string} jsonStr The string to parse.
248241
* @param {(function(string, *) : *)=} opt_reviver
249242
* @return {*} The JSON object.
250243
* @throws {Error}
251244
*/
252-
JSONType.prototype.parse = function(jsonStr, opt_reviver) {};
245+
JSON.parse = function(jsonStr, opt_reviver) {};
253246

254247

255248
/**
@@ -259,11 +252,4 @@ JSONType.prototype.parse = function(jsonStr, opt_reviver) {};
259252
* @return {string} JSON string which represents jsonObj.
260253
* @throws {Error}
261254
*/
262-
JSONType.prototype.stringify = function(jsonObj, opt_replacer, opt_space) {};
263-
264-
265-
/**
266-
* @type {!JSONType}
267-
* @suppress {duplicate}
268-
*/
269-
var JSON;
255+
JSON.stringify = function(jsonObj, opt_replacer, opt_space) {};

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

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -212,45 +212,34 @@ module Closure {
212212
or
213213
name = namespace
214214
)
215+
or
216+
name = "goog" // The closure libraries themselves use the "goog" namespace
217+
}
218+
219+
/**
220+
* Holds if a prefix of `name` is a closure namespace.
221+
*/
222+
bindingset[name]
223+
private predicate hasClosureNamespacePrefix(string name) {
224+
isClosureNamespace(name.substring(0, name.indexOf(".")))
225+
or
226+
isClosureNamespace(name)
215227
}
216228

217229
/**
218230
* Gets the closure namespace path addressed by the given data flow node, if any.
219231
*/
220232
string getClosureNamespaceFromSourceNode(DataFlow::SourceNode node) {
221-
isClosureNamespace(result) and
222-
node = DataFlow::globalVarRef(result)
223-
or
224-
exists(DataFlow::SourceNode base, string basePath, string prop |
225-
basePath = getClosureNamespaceFromSourceNode(base) and
226-
node = base.getAPropertyRead(prop) and
227-
result = basePath + "." + prop and
228-
// ensure finiteness
229-
(
230-
isClosureNamespace(basePath)
231-
or
232-
// direct access, no indirection
233-
node.(DataFlow::PropRead).getBase() = base
234-
)
235-
)
236-
or
237-
// Associate an access path with the immediate RHS of a store on a closure namespace.
238-
// This is to support patterns like:
239-
// foo.bar = { baz() {} }
240-
exists(DataFlow::PropWrite write |
241-
node = write.getRhs() and
242-
result = getWrittenClosureNamespace(write)
243-
)
244-
or
245-
result = node.(ClosureNamespaceAccess).getClosureNamespace()
233+
result = GlobalAccessPath::getAccessPath(node) and
234+
hasClosureNamespacePrefix(result)
246235
}
247236

248237
/**
249238
* Gets the closure namespace path written to by the given property write, if any.
250239
*/
251240
string getWrittenClosureNamespace(DataFlow::PropWrite node) {
252-
result = getClosureNamespaceFromSourceNode(node.getBase().getALocalSource()) + "." +
253-
node.getPropertyName()
241+
result = GlobalAccessPath::fromRhs(node.getRhs()) and
242+
hasClosureNamespacePrefix(result)
254243
}
255244

256245
/**

javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module GlobalAccessPath {
4141
* })(NS = NS || {});
4242
* ```
4343
*/
44+
cached
4445
string fromReference(DataFlow::Node node) {
4546
result = fromReference(node.getImmediatePredecessor())
4647
or
@@ -142,6 +143,7 @@ module GlobalAccessPath {
142143
* })(foo = foo || {});
143144
* ```
144145
*/
146+
cached
145147
string fromRhs(DataFlow::Node node) {
146148
exists(DataFlow::SourceNode base, string baseName, string name |
147149
node = base.getAPropertyWrite(name).getRhs() and
@@ -152,9 +154,9 @@ module GlobalAccessPath {
152154
baseName = fromRhs(base)
153155
)
154156
or
155-
exists(AssignExpr assign |
156-
node = assign.getRhs().flow() and
157-
result = assign.getLhs().(GlobalVarAccess).getName()
157+
exists(GlobalVariable var |
158+
node = var.getAnAssignedExpr().flow() and
159+
result = var.getName()
158160
)
159161
or
160162
exists(FunctionDeclStmt fun |
@@ -166,6 +168,16 @@ module GlobalAccessPath {
166168
node = DataFlow::valueNode(cls) and
167169
result = cls.getIdentifier().(GlobalVarDecl).getName()
168170
)
171+
or
172+
exists(EnumDeclaration decl |
173+
node = DataFlow::valueNode(decl) and
174+
result = decl.getIdentifier().(GlobalVarDecl).getName()
175+
)
176+
or
177+
exists(NamespaceDeclaration decl |
178+
node = DataFlow::valueNode(decl) and
179+
result = decl.getId().(GlobalVarDecl).getName()
180+
)
169181
}
170182

171183
/**

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

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ module DataFlow {
4343
} or
4444
THtmlAttributeNode(HTML::Attribute attr) or
4545
TExceptionalFunctionReturnNode(Function f) or
46-
TExceptionalInvocationReturnNode(InvokeExpr e)
46+
TExceptionalInvocationReturnNode(InvokeExpr e) or
47+
TGlobalAccessPathRoot()
4748

4849
/**
4950
* A node in the data flow graph.
@@ -120,6 +121,12 @@ module DataFlow {
120121
/** Gets a function value that may reach this node. */
121122
FunctionNode getAFunctionValue() {
122123
result.getAstNode() = analyze().getAValue().(AbstractCallable).getFunction()
124+
or
125+
exists(string name |
126+
GlobalAccessPath::isAssignedInUniqueFile(name) and
127+
GlobalAccessPath::fromRhs(result) = name and
128+
GlobalAccessPath::fromReference(this) = name
129+
)
123130
}
124131

125132
/**
@@ -912,6 +919,20 @@ module DataFlow {
912919
DataFlow::InvokeNode getInvocation() { result = invoke.flow() }
913920
}
914921

922+
/**
923+
* A pseudo-node representing the root of a global access path.
924+
*/
925+
private class GlobalAccessPathRoot extends TGlobalAccessPathRoot, DataFlow::Node {
926+
override string toString() { result = "global access path" }
927+
}
928+
929+
/**
930+
* INTERNAL. DO NOT USE.
931+
*
932+
* Gets a pseudo-node representing the root of a global access path.
933+
*/
934+
DataFlow::Node globalAccessPathRootPseudoNode() { result instanceof TGlobalAccessPathRoot }
935+
915936
/**
916937
* Provides classes representing various kinds of calls.
917938
*

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

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,7 @@ class ClassNode extends DataFlow::SourceNode {
693693
/**
694694
* Gets a dataflow node that refers to this class object.
695695
*/
696+
cached
696697
DataFlow::SourceNode getAClassReference() {
697698
result = getAClassReference(DataFlow::TypeTracker::end())
698699
}
@@ -709,6 +710,15 @@ class ClassNode extends DataFlow::SourceNode {
709710
t.start() and
710711
result = getAReceiverNode()
711712
or
713+
// Use a parameter type as starting point of type tracking.
714+
// Use `t.call()` to emulate the value being passed in through an unseen
715+
// call site, but not out of the call again.
716+
t.call() and
717+
exists(Parameter param |
718+
this = param.getTypeAnnotation().getClass() and
719+
result = DataFlow::parameterNode(param)
720+
)
721+
or
712722
result = getAnInstanceReferenceAux(t) and
713723
// Avoid tracking into the receiver of other classes.
714724
// Note that this also blocks flows into a property of the receiver,
@@ -724,6 +734,7 @@ class ClassNode extends DataFlow::SourceNode {
724734
/**
725735
* Gets a dataflow node that refers to an instance of this class.
726736
*/
737+
cached
727738
DataFlow::SourceNode getAnInstanceReference() {
728739
result = getAnInstanceReference(DataFlow::TypeTracker::end())
729740
}
@@ -844,6 +855,12 @@ module ClassNode {
844855
override DataFlow::Node getASuperClassNode() { result = astNode.getSuperClass().flow() }
845856
}
846857

858+
private DataFlow::PropRef getAPrototypeReferenceInFile(string name, File f) {
859+
GlobalAccessPath::getAccessPath(result.getBase()) = name and
860+
result.getPropertyName() = "prototype" and
861+
result.getFile() = f
862+
}
863+
847864
/**
848865
* A function definition with prototype manipulation as a `ClassNode` instance.
849866
*/
@@ -854,9 +871,16 @@ module ClassNode {
854871

855872
FunctionStyleClass() {
856873
function.getFunction() = astNode and
857-
exists(DataFlow::PropRef read |
858-
read.getPropertyName() = "prototype" and
859-
read.getBase().analyze().getAValue() = function
874+
(
875+
exists (DataFlow::PropRef read |
876+
read.getPropertyName() = "prototype" and
877+
read.getBase().analyze().getAValue() = function
878+
)
879+
or
880+
exists(string name |
881+
name = GlobalAccessPath::fromRhs(this) and
882+
exists(getAPrototypeReferenceInFile(name, getFile()))
883+
)
860884
)
861885
}
862886

@@ -916,11 +940,16 @@ module ClassNode {
916940
result = base.getAPropertyRead("prototype")
917941
or
918942
result = base.getAPropertySource("prototype")
919-
or
920-
exists(ExtendCall call |
921-
call.getDestinationOperand() = base.getAPropertyRead("prototype") and
922-
result = call.getASourceOperand()
923-
)
943+
)
944+
or
945+
exists(string name |
946+
GlobalAccessPath::fromRhs(this) = name and
947+
result = getAPrototypeReferenceInFile(name, getFile())
948+
)
949+
or
950+
exists(ExtendCall call |
951+
call.getDestinationOperand() = getAPrototypeReference() and
952+
result = call.getASourceOperand()
924953
)
925954
}
926955

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ module SourceNode {
250250
DataFlow::thisNode(this, _)
251251
or
252252
this = DataFlow::destructuredModuleImportNode(_)
253+
or
254+
this = DataFlow::globalAccessPathRootPseudoNode()
253255
}
254256
}
255257
}

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

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,6 @@ class AnalyzedNode extends DataFlow::Node {
4747
*/
4848
AnalyzedNode localFlowPred() { result = getAPredecessor() }
4949

50-
/**
51-
* INTERNAL. Do not use.
52-
*
53-
* Gets another data flow node whose value flows into this node in a global step
54-
* (this is, involving global variables).
55-
*/
56-
AnalyzedNode globalFlowPred() { none() }
57-
5850
/**
5951
* Gets an abstract value that this node may evaluate to at runtime.
6052
*
@@ -92,9 +84,6 @@ class AnalyzedNode extends DataFlow::Node {
9284
exists(DataFlow::Incompleteness cause |
9385
isIncomplete(cause) and result = TIndefiniteAbstractValue(cause)
9486
)
95-
or
96-
result = globalFlowPred().getALocalValue() and
97-
shouldTrackGlobally(result)
9887
}
9988

10089
/** Gets a type inferred for this node. */
@@ -295,8 +284,3 @@ private class AnalyzedAsyncFunction extends AnalyzedFunction {
295284

296285
override AbstractValue getAReturnValue() { result = TAbstractOtherObject() }
297286
}
298-
299-
/**
300-
* Holds if the given value should be propagated along `globalFlowPred()` edges.
301-
*/
302-
private predicate shouldTrackGlobally(AbstractValue value) { value instanceof AbstractCallable }

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ private import javascript
1010
private import internal.FlowSteps
1111

1212
private class PropertyName extends string {
13-
PropertyName() { this = any(DataFlow::PropRef pr).getPropertyName() }
13+
PropertyName() {
14+
this = any(DataFlow::PropRef pr).getPropertyName()
15+
or
16+
GlobalAccessPath::isAssignedInUniqueFile(this)
17+
}
1418
}
1519

1620
private class OptionalPropertyName extends string {
@@ -89,6 +93,20 @@ module StepSummary {
8993
or
9094
any(AdditionalTypeTrackingStep st).step(pred, succ) and
9195
summary = LevelStep()
96+
or
97+
exists(string name |
98+
name = GlobalAccessPath::fromRhs(pred) and
99+
GlobalAccessPath::isAssignedInUniqueFile(name) and
100+
succ = DataFlow::globalAccessPathRootPseudoNode() and
101+
summary = StoreStep(name)
102+
)
103+
or
104+
exists(string name |
105+
name = GlobalAccessPath::fromReference(succ) and
106+
GlobalAccessPath::isAssignedInUniqueFile(name) and
107+
pred = DataFlow::globalAccessPathRootPseudoNode() and
108+
summary = LoadStep(name)
109+
)
92110
}
93111
}
94112

@@ -158,6 +176,12 @@ class TypeTracker extends TTypeTracker {
158176
*/
159177
predicate start() { hasCall = false and prop = "" }
160178

179+
/**
180+
* Holds if this is the starting point of type tracking
181+
* when tracking a parameter into a call, but not out of it.
182+
*/
183+
predicate call() { hasCall = true and prop = "" }
184+
161185
/**
162186
* Holds if this is the end point of type tracking.
163187
*/

0 commit comments

Comments
 (0)