Skip to content

Commit 191962f

Browse files
committed
C#: Add data flow 'getARuntimeTarget' predicate to 'FunctionPointerCall'
1 parent 7d9ebaf commit 191962f

File tree

5 files changed

+319
-0
lines changed

5 files changed

+319
-0
lines changed
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/**
2+
* INTERNAL: Do not use.
3+
*
4+
* Provides classes for resolving function pointer calls.
5+
*/
6+
7+
import csharp
8+
private import dotnet
9+
private import semmle.code.csharp.dataflow.CallContext
10+
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch
11+
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
12+
private import semmle.code.csharp.dataflow.internal.DataFlowPublic
13+
private import semmle.code.csharp.dataflow.FlowSummary
14+
private import semmle.code.csharp.dispatch.Dispatch
15+
private import semmle.code.csharp.frameworks.system.linq.Expressions
16+
17+
/** A source of flow for a function pointer expression. */
18+
private class FunctionPointerFlowSource extends DataFlow::ExprNode {
19+
Callable c;
20+
21+
FunctionPointerFlowSource() {
22+
this.getExpr() =
23+
any(Expr e |
24+
c = e.(AddressOfExpr).getOperand().(CallableAccess).getTarget().getUnboundDeclaration()
25+
)
26+
}
27+
28+
/** Gets the callable that is referenced in this function pointer flow source. */
29+
Callable getCallable() { result = c }
30+
}
31+
32+
/** A sink of flow for a function pointer expression. */
33+
abstract private class FunctionPointerFlowSink extends DataFlow::Node {
34+
/**
35+
* Gets an actual run-time target of this function pointer call in the given call
36+
* context, if any. The call context records the *last* call required to
37+
* resolve the target, if any.
38+
*
39+
* See examples in `DelegateFlowSink`.
40+
*/
41+
cached
42+
Callable getARuntimeTarget(CallContext context) {
43+
exists(FunctionPointerFlowSource fptrfs |
44+
flowsFrom(this, fptrfs, _, context) and
45+
result = fptrfs.getCallable()
46+
)
47+
}
48+
}
49+
50+
/** A function pointer call expression. */
51+
class FunctionPointerCallExpr extends FunctionPointerFlowSink, DataFlow::ExprNode {
52+
FunctionPointerCall fptrc;
53+
54+
FunctionPointerCallExpr() { this.getExpr() = fptrc.getFunctionPointerExpr() }
55+
56+
/** Gets the function pointer call that this expression belongs to. */
57+
FunctionPointerCall getFunctionPointerCall() { result = fptrc }
58+
}
59+
60+
/** A non-function pointer call. */
61+
private class NonFunctionPointerCall extends Expr {
62+
private DispatchCall dc;
63+
64+
NonFunctionPointerCall() { this = dc.getCall() }
65+
66+
/**
67+
* Gets a run-time target of this call. A target is always a source
68+
* declaration, and if the callable has both CIL and source code, only
69+
* the source code version is returned.
70+
*/
71+
Callable getARuntimeTarget() { result = getCallableForDataFlow(dc.getADynamicTarget()) }
72+
73+
/** Gets the `i`th argument of this call. */
74+
Expr getArgument(int i) { result = dc.getArgument(i) }
75+
}
76+
77+
private class NormalReturnNode extends Node {
78+
NormalReturnNode() { this.(ReturnNode).getKind() instanceof NormalReturnKind }
79+
}
80+
81+
/**
82+
* Holds if data can flow (inter-procedurally) to function pointer `sink` from
83+
* `node`. This predicate searches backwards from `sink` to `node`.
84+
*
85+
* The parameter `isReturned` indicates whether the path from `sink` to
86+
* `node` goes through a returned expression. The call context `lastCall`
87+
* records the last call on the path from `node` to `sink`, if any.
88+
*/
89+
private predicate flowsFrom(
90+
FunctionPointerFlowSink sink, DataFlow::Node node, boolean isReturned, CallContext lastCall
91+
) {
92+
// Base case
93+
sink = node and
94+
isReturned = false and
95+
lastCall instanceof EmptyCallContext
96+
or
97+
// Local flow
98+
exists(DataFlow::Node mid | flowsFrom(sink, mid, isReturned, lastCall) |
99+
LocalFlow::localFlowStepCommon(node, mid)
100+
or
101+
exists(Ssa::Definition def |
102+
LocalFlow::localSsaFlowStep(def, node, mid) and
103+
LocalFlow::usesInstanceField(def)
104+
)
105+
)
106+
or
107+
// Flow through static field or property
108+
exists(DataFlow::Node mid |
109+
flowsFrom(sink, mid, _, _) and
110+
jumpStep(node, mid) and
111+
isReturned = false and
112+
lastCall instanceof EmptyCallContext
113+
)
114+
or
115+
// Flow into a callable (non-function pointer call)
116+
exists(ParameterNode mid, CallContext prevLastCall, NonFunctionPointerCall call, Parameter p |
117+
flowsFrom(sink, mid, isReturned, prevLastCall) and
118+
isReturned = false and
119+
p = mid.getParameter() and
120+
flowIntoNonFunctionPointerCall(call, node.asExpr(), p) and
121+
lastCall = getLastCall(prevLastCall, call, p.getPosition())
122+
)
123+
or
124+
// Flow into a callable (function pointer call)
125+
exists(
126+
ParameterNode mid, CallContext prevLastCall, FunctionPointerCall call, Callable c, Parameter p,
127+
int i
128+
|
129+
flowsFrom(sink, mid, isReturned, prevLastCall) and
130+
isReturned = false and
131+
flowIntoFunctionPointerCall(call, c, node.asExpr(), i) and
132+
c.getParameter(i) = p and
133+
p = mid.getParameter() and
134+
lastCall = getLastCall(prevLastCall, call, i)
135+
)
136+
or
137+
// Flow out of a callable (non-function pointer call).
138+
exists(DataFlow::ExprNode mid |
139+
flowsFrom(sink, mid, _, lastCall) and
140+
isReturned = true and
141+
flowOutOfNonFunctionPointerCall(mid.getExpr(), node)
142+
)
143+
or
144+
// Flow out of a callable (function pointer call).
145+
exists(DataFlow::ExprNode mid |
146+
flowsFrom(sink, mid, _, _) and
147+
isReturned = true and
148+
flowOutOfFunctionPointerCall(mid.getExpr(), node, lastCall)
149+
)
150+
}
151+
152+
/**
153+
* Gets the last call when tracking flow into `call`. The context
154+
* `prevLastCall` is the previous last call, so the result is the
155+
* previous call if it exists, otherwise `call` is the last call.
156+
*/
157+
bindingset[call, i]
158+
private CallContext getLastCall(CallContext prevLastCall, Expr call, int i) {
159+
prevLastCall instanceof EmptyCallContext and
160+
result.(ArgumentCallContext).isArgument(call, i)
161+
or
162+
prevLastCall instanceof ArgumentCallContext and
163+
result = prevLastCall
164+
}
165+
166+
pragma[noinline]
167+
private predicate flowIntoNonFunctionPointerCall(
168+
NonFunctionPointerCall call, Expr arg, DotNet::Parameter p
169+
) {
170+
exists(DotNet::Callable callable, int i |
171+
callable = call.getARuntimeTarget() and
172+
p = callable.getAParameter() and
173+
arg = call.getArgument(i) and
174+
i = p.getPosition()
175+
)
176+
}
177+
178+
pragma[noinline]
179+
private predicate flowIntoFunctionPointerCall(FunctionPointerCall call, Callable c, Expr arg, int i) {
180+
exists(FunctionPointerFlowSource fptrfs, FunctionPointerCallExpr fptrce |
181+
// the call context is irrelevant because the function pointer call
182+
// itself will be the context
183+
flowsFrom(fptrce, fptrfs, _, _) and
184+
arg = call.getArgument(i) and
185+
c = fptrfs.getCallable() and
186+
call = fptrce.getFunctionPointerCall()
187+
)
188+
}
189+
190+
pragma[noinline]
191+
private predicate flowOutOfNonFunctionPointerCall(NonFunctionPointerCall call, NormalReturnNode ret) {
192+
call.getARuntimeTarget() = ret.getEnclosingCallable()
193+
}
194+
195+
pragma[noinline]
196+
private predicate flowOutOfFunctionPointerCall(
197+
FunctionPointerCall call, NormalReturnNode ret, CallContext lastCall
198+
) {
199+
exists(FunctionPointerFlowSource fptrfs, FunctionPointerCallExpr fptrce, Callable c |
200+
flowsFrom(fptrce, fptrfs, _, lastCall) and
201+
ret.getEnclosingCallable() = c and
202+
c = fptrfs.getCallable() and
203+
call = fptrce.getFunctionPointerCall()
204+
)
205+
}

csharp/ql/src/semmle/code/csharp/exprs/Call.qll

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Expr
88
import semmle.code.csharp.Callable
99
import semmle.code.csharp.dataflow.CallContext as CallContext
1010
private import semmle.code.csharp.dataflow.internal.DelegateDataFlow
11+
private import semmle.code.csharp.dataflow.internal.FunctionPointerDataFlow
1112
private import semmle.code.csharp.dispatch.Dispatch
1213
private import dotnet
1314

@@ -618,8 +619,24 @@ class DelegateCall extends Call, @delegate_invocation_expr {
618619
class FunctionPointerCall extends Call, @function_pointer_invocation_expr {
619620
override Callable getTarget() { none() }
620621

622+
/**
623+
* Gets a potential run-time target of this function pointer call in the given
624+
* call context `cc`.
625+
*/
626+
Callable getARuntimeTarget(CallContext::CallContext cc) {
627+
exists(FunctionPointerCallExpr call |
628+
this = call.getFunctionPointerCall() and
629+
result = call.getARuntimeTarget(cc)
630+
)
631+
}
632+
633+
override Callable getARuntimeTarget() { result = getARuntimeTarget(_) }
634+
621635
override Expr getRuntimeArgument(int i) { result = getArgument(i) }
622636

637+
/** Gets the function pointer expression of this call. */
638+
Expr getFunctionPointerExpr() { result = this.getChild(-1) }
639+
623640
override string toString() { result = "function pointer call" }
624641

625642
override string getAPrimaryQlClass() { result = "FunctionPointerCall" }
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System;
2+
3+
unsafe class FunctionPointerFlow
4+
{
5+
public static void Log1(int i) { }
6+
public static void Log2(int i) { }
7+
public static void Log3(int i) { }
8+
public static void Log4(int i) { }
9+
public static void Log5(int i) { }
10+
public static void Log6(int i) { }
11+
12+
public static void M2(delegate*<int, void> a)
13+
{
14+
a(1);
15+
a = &Log3;
16+
a(2);
17+
}
18+
19+
public void M3()
20+
{
21+
M2(&Log1);
22+
}
23+
24+
public static void M4(delegate*<int, void> a)
25+
{
26+
M2(a);
27+
}
28+
29+
public void M5()
30+
{
31+
M4(&Log2);
32+
}
33+
34+
public delegate*<int, void> M6()
35+
{
36+
return &Log4;
37+
}
38+
39+
public void M7()
40+
{
41+
M6()(0);
42+
}
43+
44+
public void M8()
45+
{
46+
static void LocalFunction(int i) { };
47+
M2(&LocalFunction);
48+
}
49+
50+
private static delegate*<int, void> field = &Log5;
51+
52+
public void M9()
53+
{
54+
field(1);
55+
}
56+
57+
public void M10(delegate*<delegate*<int, void>, void> aa, delegate*<int, void> a)
58+
{
59+
aa(a);
60+
}
61+
62+
public void M11()
63+
{
64+
M10(&M4, &Log6);
65+
}
66+
67+
public void M16(delegate*<Action<int>, void> aa, Action<int> a)
68+
{
69+
aa(a);
70+
}
71+
72+
public static void M17(Action<int> a)
73+
{
74+
a(0);
75+
a = _ => { };
76+
a(0);
77+
}
78+
79+
public void M18()
80+
{
81+
M16(&M17, (i) => { });
82+
}
83+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
| FunctionPointerFlow.cs:14:9:14:12 | function pointer call | FunctionPointerFlow.cs:5:24:5:27 | Log1 | FunctionPointerFlow.cs:21:12:21:16 | &... |
2+
| FunctionPointerFlow.cs:14:9:14:12 | function pointer call | FunctionPointerFlow.cs:6:24:6:27 | Log2 | FunctionPointerFlow.cs:26:12:26:12 | access to parameter a |
3+
| FunctionPointerFlow.cs:14:9:14:12 | function pointer call | FunctionPointerFlow.cs:10:24:10:27 | Log6 | FunctionPointerFlow.cs:26:12:26:12 | access to parameter a |
4+
| FunctionPointerFlow.cs:14:9:14:12 | function pointer call | FunctionPointerFlow.cs:46:9:46:44 | LocalFunction | FunctionPointerFlow.cs:47:12:47:25 | &... |
5+
| FunctionPointerFlow.cs:16:9:16:12 | function pointer call | FunctionPointerFlow.cs:7:24:7:27 | Log3 | file://:0:0:0:0 | <empty> |
6+
| FunctionPointerFlow.cs:41:9:41:15 | function pointer call | FunctionPointerFlow.cs:8:24:8:27 | Log4 | file://:0:0:0:0 | <empty> |
7+
| FunctionPointerFlow.cs:54:9:54:16 | function pointer call | FunctionPointerFlow.cs:9:24:9:27 | Log5 | file://:0:0:0:0 | <empty> |
8+
| FunctionPointerFlow.cs:59:9:59:13 | function pointer call | FunctionPointerFlow.cs:24:24:24:25 | M4 | FunctionPointerFlow.cs:64:13:64:15 | &... |
9+
| FunctionPointerFlow.cs:69:9:69:13 | function pointer call | FunctionPointerFlow.cs:72:24:72:26 | M17 | FunctionPointerFlow.cs:81:13:81:16 | &... |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import csharp
2+
3+
query predicate fptrCall(FunctionPointerCall fptrc, Callable c, CallContext::CallContext cc) {
4+
c = fptrc.getARuntimeTarget(cc)
5+
}

0 commit comments

Comments
 (0)