|
| 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 | +} |
0 commit comments