Skip to content

Commit 0a0e9bb

Browse files
authored
Merge pull request github#13767 from owen-mc/go/missing-flow-through-receiver
Go: Fix missing flow through receiver for function variable
2 parents a9c76d4 + b9027a0 commit 0a0e9bb

File tree

16 files changed

+79
-42
lines changed

16 files changed

+79
-42
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: fix
3+
---
4+
* The definition of `DataFlow::MethodCallNode` has been expanded to include `DataFlow::CallNode`s where what is being called is a variable that has a method assigned to it within the calling function. This means we can now follow data flow into the receiver of such a method call.

go/ql/lib/semmle/go/controlflow/IR.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -744,7 +744,7 @@ module IR {
744744

745745
override Type getResultType() {
746746
exists(CallExpr c | this.getBase() = evalExprInstruction(c) |
747-
result = c.getTarget().getResultType(i)
747+
result = c.getCalleeType().getResultType(i)
748748
)
749749
or
750750
exists(Expr e | this.getBase() = evalExprInstruction(e) |

go/ql/lib/semmle/go/dataflow/internal/DataFlowDispatch.qll

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ private import DataFlowPrivate
88
private predicate isInterfaceCallReceiver(
99
DataFlow::CallNode call, DataFlow::Node recv, InterfaceType tp, string m
1010
) {
11-
call.getReceiver() = recv and
11+
call.(DataFlow::MethodCallNode).getReceiver() = recv and
1212
recv.getType().getUnderlyingType() = tp and
1313
m = call.getACalleeIncludingExternals().asFunction().getName()
1414
}
@@ -149,7 +149,9 @@ predicate golangSpecificParamArgFilter(
149149
// Interface methods calls may be passed strictly to that exact method's model receiver:
150150
arg.getPosition() != -1
151151
or
152-
exists(Function callTarget | callTarget = call.getNode().(DataFlow::CallNode).getTarget() |
152+
exists(Function callTarget |
153+
callTarget = call.getNode().(DataFlow::CallNode).getACalleeIncludingExternals().asFunction()
154+
|
153155
not isInterfaceMethod(callTarget)
154156
or
155157
callTarget = p.getCallable().asSummarizedCallable().asFunction()

go/ql/lib/semmle/go/dataflow/internal/DataFlowNodes.qll

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -439,8 +439,8 @@ module Public {
439439
CallNode getCallNode() { result = call }
440440

441441
override Type getType() {
442-
exists(Function f | f = call.getTarget() |
443-
result = f.getParameterType(f.getNumParameter() - 1)
442+
exists(SignatureType t | t = call.getCall().getCalleeType() |
443+
result = t.getParameterType(t.getNumParameter() - 1)
444444
)
445445
}
446446

@@ -474,7 +474,11 @@ module Public {
474474
class CallNode extends ExprNode {
475475
override CallExpr expr;
476476

477-
/** Gets the declared target of this call */
477+
/**
478+
* Gets the declared target of this call, if it exists.
479+
*
480+
* This doesn't exist when a function is called via a variable.
481+
*/
478482
Function getTarget() { result = expr.getTarget() }
479483

480484
private DataFlow::Node getACalleeSource() { result = getACalleeSource(this) }
@@ -622,16 +626,40 @@ module Public {
622626
/** Gets a result of this call. */
623627
Node getAResult() { result = this.getResult(_) }
624628

625-
/** Gets the data flow node corresponding to the receiver of this call, if any. */
629+
/**
630+
* Gets the data flow node corresponding to the receiver of this call, if any.
631+
*
632+
* When a method value is assigned to a variable then when it is called it
633+
* looks like a function call, as in the following example.
634+
* ```go
635+
* file, _ := os.Open("test.txt")
636+
* f := file.Close
637+
* f()
638+
* ```
639+
* In this case we use local flow to try to find the receiver (`file` in
640+
* the above example).
641+
*/
626642
Node getReceiver() { result = this.getACalleeSource().(MethodReadNode).getReceiver() }
627643

628644
/** Holds if this call has an ellipsis after its last argument. */
629645
predicate hasEllipsis() { expr.hasEllipsis() }
630646
}
631647

632-
/** A data flow node that represents a call to a method. */
648+
/**
649+
* A data flow node that represents a call to a method.
650+
*
651+
* When a method value is assigned to a variable then when it is called it
652+
* syntactically looks like a function call, as in the following example.
653+
* ```go
654+
* file, _ := os.Open("test.txt")
655+
* f := file.Close
656+
* f()
657+
* ```
658+
* In this case we use local flow to see whether a method is assigned to the
659+
* function.
660+
*/
633661
class MethodCallNode extends CallNode {
634-
MethodCallNode() { expr.getTarget() instanceof Method }
662+
MethodCallNode() { getACalleeSource(this) instanceof MethodReadNode }
635663

636664
override Method getTarget() { result = expr.getTarget() }
637665

go/ql/lib/semmle/go/frameworks/Gqlgen.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module Gqlgen {
77
/** An autogenerated file containing gqlgen code. */
88
private class GqlgenGeneratedFile extends File {
99
GqlgenGeneratedFile() {
10-
exists(DataFlow::CallNode call |
10+
exists(DataFlow::MethodCallNode call |
1111
call.getReceiver().getType().hasQualifiedName("github.com/99designs/gqlgen/graphql", _) and
1212
call.getFile() = this
1313
)

go/ql/lib/semmle/go/frameworks/stdlib/NetHttp.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ module NetHttp {
131131
)
132132
or
133133
stack = SummaryComponentStack::argument(-1) and
134-
result = call.getReceiver()
134+
result = call.(DataFlow::MethodCallNode).getReceiver()
135135
}
136136

137137
private class ResponseBody extends Http::ResponseBody::Range {

go/ql/lib/semmle/go/security/ExternalAPIs.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class ExternalApiDataNode extends DataFlow::Node {
8686
this = call.getArgument(i)
8787
or
8888
// Receiver to a call to a method which returns non trivial value
89-
this = call.getReceiver() and
89+
this = call.(DataFlow::MethodCallNode).getReceiver() and
9090
i = -1
9191
) and
9292
// Not defined in the code that is being analyzed

go/ql/lib/semmle/go/security/SafeUrlFlowCustomizations.qll

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ module SafeUrlFlow {
3333

3434
/** A function model step using `UnsafeUrlMethod`, considered as a sanitizer for safe URL flow. */
3535
private class UnsafeUrlMethodEdge extends SanitizerEdge {
36-
UnsafeUrlMethodEdge() { this = any(UnsafeUrlMethod um).getACall().getReceiver() }
36+
UnsafeUrlMethodEdge() {
37+
this = any(UnsafeUrlMethod um).getACall().(DataFlow::MethodCallNode).getReceiver()
38+
}
3739
}
3840

3941
/** Any slicing of the URL, considered as a sanitizer for safe URL flow. */

go/ql/src/InconsistentCode/UnhandledCloseWritableHandle.ql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ predicate isWritableFileHandle(DataFlow::Node source, DataFlow::CallNode call) {
9090
/**
9191
* Holds if `os.File.Close` is called on `sink`.
9292
*/
93-
predicate isCloseSink(DataFlow::Node sink, DataFlow::CallNode closeCall) {
93+
predicate isCloseSink(DataFlow::Node sink, DataFlow::MethodCallNode closeCall) {
9494
// find calls to the os.File.Close function
9595
closeCall = any(CloseFileFun f).getACall() and
9696
// that are unhandled
@@ -115,7 +115,7 @@ predicate isCloseSink(DataFlow::Node sink, DataFlow::CallNode closeCall) {
115115
* Holds if `os.File.Sync` is called on `sink` and the result of the call is neither
116116
* deferred nor discarded.
117117
*/
118-
predicate isHandledSync(DataFlow::Node sink, DataFlow::CallNode syncCall) {
118+
predicate isHandledSync(DataFlow::Node sink, DataFlow::MethodCallNode syncCall) {
119119
// find a call of the `os.File.Sync` function
120120
syncCall = any(SyncFileFun f).getACall() and
121121
// match the sink with the object on which the method is called

go/ql/src/Security/CWE-352/ConstantOauth2State.ql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ class PrivateUrlFlowsToAuthCodeUrlCall extends DataFlow::Configuration {
113113
)
114114
}
115115

116-
predicate isSinkCall(DataFlow::Node sink, DataFlow::CallNode call) {
116+
predicate isSinkCall(DataFlow::Node sink, DataFlow::MethodCallNode call) {
117117
exists(AuthCodeUrl m | call = m.getACall() | sink = call.getReceiver())
118118
}
119119

0 commit comments

Comments
 (0)