Skip to content

Commit 2318752

Browse files
committed
python: add reads of captured variables to
type tracking and the API graph. - In `TypeTrackerSpecific.qll` we add a jump step - to every scope entry definition - from the value of any defining `DefinitionNode` (In our example, the definition is the class name, `Users`, while the assigned value is the class definition, and it is the latter which receives flow in this case.) - In `LocalSources.qll` we allow scope entry definitions as local sources. - This feels natural enough, as they are a local source for the value, they represent. It is perhaps a bit funne to see an Ssa variable here, rather than a control flow node. - This is necessary in order for type tracking to see the local flow from the scope entry definition. - In `ApiGraphs.qll` we no longer restrict the result of `trackUseNode` to be an `ExprNode`. To keep the positive formulation, we do not prohibit module variable nodes. Instead we restrict to the new `LocalSourceNodeNotModule` which avoids those cases.
1 parent 7e003f6 commit 2318752

File tree

8 files changed

+36
-6
lines changed

8 files changed

+36
-6
lines changed

python/ql/lib/semmle/python/ApiGraphs.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -987,7 +987,7 @@ module API {
987987
DataFlow::LocalSourceNode trackUseNode(DataFlow::LocalSourceNode src) {
988988
Stages::TypeTracking::ref() and
989989
result = trackUseNode(src, DataFlow::TypeTracker::end()) and
990-
result instanceof DataFlow::ExprNode
990+
result instanceof DataFlow::LocalSourceNodeNotModule
991991
}
992992

993993
/**

python/ql/lib/semmle/python/dataflow/new/internal/LocalSources.qll

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ class LocalSourceNode extends Node {
5151
// We explicitly include any read of a global variable, as some of these may have local flow going
5252
// into them.
5353
this = any(ModuleVariableNode mvn).getARead()
54+
or
55+
// We include all scope entry definitions, as these act as the local source within the scope they
56+
// enter.
57+
this.asVar() instanceof ScopeEntryDefinition
5458
}
5559

5660
/** Holds if this `LocalSourceNode` can flow to `nodeTo` in one or more local flow steps. */
@@ -133,6 +137,19 @@ class LocalSourceNode extends Node {
133137
LocalSourceNode backtrack(TypeBackTracker t2, TypeBackTracker t) { t2 = t.step(result, this) }
134138
}
135139

140+
/**
141+
* A LocalSourceNode that is not a ModuleVariableNode
142+
* This class provides a positive formulation of that in its charpred.
143+
*/
144+
class LocalSourceNodeNotModule extends LocalSourceNode {
145+
cached
146+
LocalSourceNodeNotModule() {
147+
this instanceof ExprNode
148+
or
149+
this.asVar() instanceof ScopeEntryDefinition
150+
}
151+
}
152+
136153
/**
137154
* A node that can be used for type tracking or type back-tracking.
138155
*

python/ql/lib/semmle/python/dataflow/new/internal/TypeTrackerSpecific.qll

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,19 @@ predicate compatibleContents(TypeTrackerContent storeContent, TypeTrackerContent
4343

4444
predicate simpleLocalFlowStep = DataFlowPrivate::simpleLocalFlowStepForTypetracking/2;
4545

46-
predicate jumpStep = DataFlowPrivate::jumpStepSharedWithTypeTracker/2;
46+
predicate jumpStep(Node nodeFrom, Node nodeTo) {
47+
DataFlowPrivate::jumpStepSharedWithTypeTracker(nodeFrom, nodeTo)
48+
or
49+
capturedJumpStep(nodeFrom, nodeTo)
50+
}
51+
52+
predicate capturedJumpStep(Node nodeFrom, Node nodeTo) {
53+
exists(SsaSourceVariable var, DefinitionNode def | var.hasDefiningNode(def) |
54+
nodeTo.asVar().(ScopeEntryDefinition).getSourceVariable() = var and
55+
nodeFrom.asCfgNode() = def.getValue() and
56+
var.getScope().getScope*() = nodeFrom.getScope()
57+
)
58+
}
4759

4860
/** Holds if there is a level step from `nodeFrom` to `nodeTo`, which may depend on the call graph. */
4961
predicate levelStepCall(Node nodeFrom, Node nodeTo) { none() }

python/ql/test/library-tests/ApiGraphs/py3/dataflow-consistency.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ uniqueCallEnclosingCallable
33
| test_captured.py:7:22:7:25 | p() | Call should have one enclosing callable but has 0. |
44
| test_captured.py:7:22:7:25 | p() | Call should have one enclosing callable but has 0. |
55
| test_captured.py:14:26:14:30 | pp() | Call should have one enclosing callable but has 0. |
6+
| test_captured.py:14:26:14:30 | pp() | Call should have one enclosing callable but has 0. |
67
uniqueType
78
uniqueNodeLocation
89
missingLocation

python/ql/test/library-tests/ApiGraphs/py3/test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def use_of_builtins():
8989
def imported_builtins():
9090
import builtins #$ use=moduleImport("builtins")
9191
def open(f):
92-
return builtins.open(f) #$ MISSING: use=moduleImport("builtins").getMember("open").getReturn()
92+
return builtins.open(f) #$ use=moduleImport("builtins").getMember("open").getReturn()
9393

9494
def redefine_print():
9595
def my_print(x):

python/ql/test/library-tests/ApiGraphs/py3/test_captured.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ def pp(x):
1111
return escape(x) #$ use=moduleImport("html").getMember("escape").getReturn()
1212

1313
def pp_list_inner(l):
14-
return ", ".join(pp(x) for x in l) #$ MISSING: use=moduleImport("html").getMember("escape").getReturn()
14+
return ", ".join(pp(x) for x in l) #$ use=moduleImport("html").getMember("escape").getReturn()

python/ql/test/library-tests/ApiGraphs/py3/test_captured_flask.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,6 @@ def users(id):
2121
if not sid:
2222
return make_response(jsonify({'Error':'Token check failed: {0}'.format(sid)}))
2323
try:
24-
user = Users.query.filter_by(id=id).first() #$ MISSING: use=moduleImport("flask_sqlalchemy").getMember("SQLAlchemy").getReturn().getMember("Model").getASubclass().getMember("query").getMember("filter_by")
24+
user = Users.query.filter_by(id=id).first() #$ use=moduleImport("flask_sqlalchemy").getMember("SQLAlchemy").getReturn().getMember("Model").getASubclass().getMember("query").getMember("filter_by")
2525
except Exception as e:
2626
return make_response(jsonify({'error':str(e)}),500)

python/ql/test/library-tests/ApiGraphs/py3/test_import_star.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ def func2():
3232
def func3():
3333
var2 = print #$ use=moduleImport("builtins").getMember("print")
3434
def func4():
35-
var2() #$ MISSING: use=moduleImport("builtins").getMember("print").getReturn()
35+
var2() #$ use=moduleImport("builtins").getMember("print").getReturn()
3636
func4()

0 commit comments

Comments
 (0)