Skip to content

Commit e05d6e7

Browse files
authored
Merge pull request github#6064 from tausbn/python-add-get-method-call
Python: Add `getAMethodCall` to `LocalSourceNode`
2 parents ae296fc + 768cab3 commit e05d6e7

File tree

10 files changed

+102
-37
lines changed

10 files changed

+102
-37
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
lgtm,codescanning
2+
* A new class `DataFlow::MethodCallNode` extends `DataFlow::CallCfgNode` with convenient methods for
3+
accessing the receiver and method name of a method call.
4+
* The `LocalSourceNode` class now has a `getAMethodCall` method, with which one can easily access
5+
method calls with the given node as a receiver.

python/ql/src/Security/CWE-327/Ssl.qll

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,10 @@ API::Node sslContextInstance() {
3939
result = API::moduleImport("ssl").getMember(["SSLContext", "create_default_context"]).getReturn()
4040
}
4141

42-
class WrapSocketCall extends ConnectionCreation, DataFlow::CallCfgNode {
42+
class WrapSocketCall extends ConnectionCreation, DataFlow::MethodCallNode {
4343
WrapSocketCall() { this = sslContextInstance().getMember("wrap_socket").getACall() }
4444

45-
override DataFlow::Node getContext() {
46-
result = this.getFunction().(DataFlow::AttrRead).getObject()
47-
}
45+
override DataFlow::Node getContext() { result = this.getObject() }
4846
}
4947

5048
class OptionsAugOr extends ProtocolRestriction, DataFlow::CfgNode {

python/ql/src/experimental/semmle/python/frameworks/Stdlib.qll

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,14 @@ private module Re {
6464
*
6565
* See https://docs.python.org/3/library/re.html#regular-expression-objects
6666
*/
67-
private class CompiledRegex extends DataFlow::CallCfgNode, RegexExecution::Range {
67+
private class CompiledRegex extends DataFlow::MethodCallNode, RegexExecution::Range {
6868
DataFlow::Node regexNode;
6969

7070
CompiledRegex() {
71-
exists(DataFlow::CallCfgNode patternCall, DataFlow::AttrRead reMethod |
72-
this.getFunction() = reMethod and
71+
exists(DataFlow::MethodCallNode patternCall |
7372
patternCall = API::moduleImport("re").getMember("compile").getACall() and
74-
patternCall.flowsTo(reMethod.getObject()) and
75-
reMethod.getAttributeName() instanceof RegexExecutionMethods and
73+
patternCall.flowsTo(this.getObject()) and
74+
this.getMethodName() instanceof RegexExecutionMethods and
7675
regexNode = patternCall.getArg(0)
7776
)
7877
}

python/ql/src/semmle/python/dataflow/new/internal/Attributes.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ private class ClassDefinitionAsAttrWrite extends AttrWrite, CfgNode {
191191
* - Dynamic attribute reads using `getattr`: `getattr(object, attr)`
192192
* - Qualified imports: `from module import attr as name`
193193
*/
194-
abstract class AttrRead extends AttrRef, Node { }
194+
abstract class AttrRead extends AttrRef, Node, LocalSourceNode { }
195195

196196
/** A simple attribute read, e.g. `object.attr` */
197197
private class AttributeReadAsAttrRead extends AttrRead, CfgNode {

python/ql/src/semmle/python/dataflow/new/internal/DataFlowPublic.qll

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,45 @@ class CallCfgNode extends CfgNode, LocalSourceNode {
180180
Node getArgByName(string name) { result.asCfgNode() = node.getArgByName(name) }
181181
}
182182

183+
/**
184+
* A data-flow node corresponding to a method call, that is `foo.bar(...)`.
185+
*
186+
* Also covers the case where the method lookup is done separately from the call itself, as in
187+
* `temp = foo.bar; temp(...)`. Note that this is only tracked through local scope.
188+
*/
189+
class MethodCallNode extends CallCfgNode {
190+
AttrRead method_lookup;
191+
192+
MethodCallNode() { method_lookup = this.getFunction().getALocalSource() }
193+
194+
/**
195+
* Gets the name of the method being invoked (the `bar` in `foo.bar(...)`) if it can be determined.
196+
*
197+
* Note that this method may have multiple results if a single call node represents calls to
198+
* multiple different objects and methods. If you want to link up objects and method names
199+
* accurately, use the `calls` method instead.
200+
*/
201+
string getMethodName() { result = method_lookup.getAttributeName() }
202+
203+
/**
204+
* Gets the data-flow node corresponding to the object receiving this call. That is, the `foo` in
205+
* `foo.bar(...)`.
206+
*
207+
* Note that this method may have multiple results if a single call node represents calls to
208+
* multiple different objects and methods. If you want to link up objects and method names
209+
* accurately, use the `calls` method instead.
210+
*/
211+
Node getObject() { result = method_lookup.getObject() }
212+
213+
/** Holds if this data-flow node calls method `methodName` on the object node `object`. */
214+
predicate calls(Node object, string methodName) {
215+
// As `getObject` and `getMethodName` may both have multiple results, we must look up the object
216+
// and method name directly on `method_lookup`.
217+
object = method_lookup.getObject() and
218+
methodName = method_lookup.getAttributeName()
219+
}
220+
}
221+
183222
/**
184223
* An expression, viewed as a node in a data flow graph.
185224
*

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,16 @@ class LocalSourceNode extends Node {
7878
*/
7979
CallCfgNode getACall() { Cached::call(this, result) }
8080

81+
/**
82+
* Gets a call to the method `methodName` on this node.
83+
*
84+
* Includes both calls that have the syntactic shape of a method call (as in `obj.m(...)`), and
85+
* calls where the callee undergoes some additional local data flow (as in `tmp = obj.m; m(...)`).
86+
*/
87+
MethodCallNode getAMethodCall(string methodName) {
88+
result = this.getAnAttributeRead(methodName).getACall()
89+
}
90+
8191
/**
8292
* Gets a node that this node may flow to using one heap and/or interprocedural step.
8393
*

python/ql/src/semmle/python/frameworks/Cryptography.qll

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,7 @@ private module CryptographyModel {
228228
/** Gets a reference to the encryptor of a Cipher instance using algorithm with `algorithmName`. */
229229
DataFlow::LocalSourceNode cipherEncryptor(DataFlow::TypeTracker t, string algorithmName) {
230230
t.start() and
231-
exists(DataFlow::AttrRead attr |
232-
result.(DataFlow::CallCfgNode).getFunction() = attr and
233-
attr.getAttributeName() = "encryptor" and
234-
attr.getObject() = cipherInstance(algorithmName)
235-
)
231+
result.(DataFlow::MethodCallNode).calls(cipherInstance(algorithmName), "encryptor")
236232
or
237233
exists(DataFlow::TypeTracker t2 | result = cipherEncryptor(t2, algorithmName).track(t2, t))
238234
}
@@ -249,11 +245,7 @@ private module CryptographyModel {
249245
/** Gets a reference to the dncryptor of a Cipher instance using algorithm with `algorithmName`. */
250246
DataFlow::LocalSourceNode cipherDecryptor(DataFlow::TypeTracker t, string algorithmName) {
251247
t.start() and
252-
exists(DataFlow::AttrRead attr |
253-
result.(DataFlow::CallCfgNode).getFunction() = attr and
254-
attr.getAttributeName() = "decryptor" and
255-
attr.getObject() = cipherInstance(algorithmName)
256-
)
248+
result.(DataFlow::MethodCallNode).calls(cipherInstance(algorithmName), "decryptor")
257249
or
258250
exists(DataFlow::TypeTracker t2 | result = cipherDecryptor(t2, algorithmName).track(t2, t))
259251
}
@@ -271,18 +263,14 @@ private module CryptographyModel {
271263
* An encrypt or decrypt operation from `cryptography.hazmat.primitives.ciphers`.
272264
*/
273265
class CryptographyGenericCipherOperation extends Cryptography::CryptographicOperation::Range,
274-
DataFlow::CallCfgNode {
266+
DataFlow::MethodCallNode {
275267
string algorithmName;
276268

277269
CryptographyGenericCipherOperation() {
278-
exists(DataFlow::AttrRead attr |
279-
this.getFunction() = attr and
280-
attr.getAttributeName() = ["update", "update_into"] and
281-
(
282-
attr.getObject() = cipherEncryptor(algorithmName)
283-
or
284-
attr.getObject() = cipherDecryptor(algorithmName)
285-
)
270+
exists(DataFlow::Node object, string method |
271+
object in [cipherEncryptor(algorithmName), cipherDecryptor(algorithmName)] and
272+
method in ["update", "update_into"] and
273+
this.calls(object, method)
286274
)
287275
}
288276

@@ -337,16 +325,10 @@ private module CryptographyModel {
337325
* An hashing operation from `cryptography.hazmat.primitives.hashes`.
338326
*/
339327
class CryptographyGenericHashOperation extends Cryptography::CryptographicOperation::Range,
340-
DataFlow::CallCfgNode {
328+
DataFlow::MethodCallNode {
341329
string algorithmName;
342330

343-
CryptographyGenericHashOperation() {
344-
exists(DataFlow::AttrRead attr |
345-
this.getFunction() = attr and
346-
attr.getAttributeName() = "update" and
347-
attr.getObject() = hashInstance(algorithmName)
348-
)
349-
}
331+
CryptographyGenericHashOperation() { this.calls(hashInstance(algorithmName), "update") }
350332

351333
override Cryptography::CryptographicAlgorithm getAlgorithm() {
352334
result.matchesName(algorithmName)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
conjunctive_lookup
2+
| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj1 | bar |
3+
| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj1 | foo |
4+
| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj2 | bar |
5+
| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj2 | foo |
6+
calls_lookup
7+
| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj1 | foo |
8+
| test.py:6:1:6:6 | ControlFlowNode for meth() | meth() | obj2 | bar |
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
if cond:
2+
meth = obj1.foo
3+
else:
4+
meth = obj2.bar
5+
6+
meth()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import python
2+
import semmle.python.dataflow.new.DataFlow
3+
import experimental.dataflow.TestUtil.PrintNode
4+
5+
query predicate conjunctive_lookup(
6+
DataFlow::MethodCallNode methCall, string call, string object, string methodName
7+
) {
8+
call = prettyNode(methCall) and
9+
object = prettyNode(methCall.getObject()) and
10+
methodName = methCall.getMethodName()
11+
}
12+
13+
query predicate calls_lookup(
14+
DataFlow::MethodCallNode methCall, string call, string object, string methodName
15+
) {
16+
call = prettyNode(methCall) and
17+
exists(DataFlow::Node o | methCall.calls(o, methodName) and object = prettyNode(o))
18+
}

0 commit comments

Comments
 (0)