|
1 | 1 | private import cpp |
2 | 2 | private import semmle.code.cpp.ir.IR |
| 3 | +private import semmle.code.cpp.ir.dataflow.DataFlow |
3 | 4 | private import DataFlowPrivate as DataFlowPrivate |
4 | 5 | private import DataFlowUtil |
5 | 6 | private import DataFlowImplCommon as DataFlowImplCommon |
| 7 | +private import codeql.typetracking.TypeTracking |
| 8 | +private import SsaImpl as SsaImpl |
6 | 9 |
|
7 | 10 | /** |
8 | 11 | * Holds if `f` has name `qualifiedName` and `nparams` parameter count. This is |
@@ -81,174 +84,200 @@ private DataFlowPrivate::DataFlowCallable nonVirtualDispatch(DataFlowPrivate::Da |
81 | 84 | .viableTarget(call.asCallInstruction().getUnconvertedResultExpression()) |
82 | 85 | } |
83 | 86 |
|
| 87 | +private class RelevantNode extends Node { |
| 88 | + RelevantNode() { this.getType().stripType() instanceof Class } |
| 89 | +} |
| 90 | + |
| 91 | +private signature DataFlowPrivate::DataFlowCallable methodDispatchSig( |
| 92 | + DataFlowPrivate::DataFlowCall c |
| 93 | +); |
| 94 | + |
| 95 | +private predicate ignoreConstructor(Expr e) { |
| 96 | + e instanceof ConstructorDirectInit or |
| 97 | + e instanceof ConstructorVirtualInit or |
| 98 | + e instanceof ConstructorDelegationInit or |
| 99 | + exists(ConstructorFieldInit init | init.getExpr() = e) |
| 100 | +} |
| 101 | + |
84 | 102 | /** |
85 | | - * Provides virtual dispatch support compatible with the original |
86 | | - * implementation of `semmle.code.cpp.security.TaintTracking`. |
| 103 | + * Holds if `n` is either: |
| 104 | + * - the post-update node of a qualifier after a call to a constructor which |
| 105 | + * constructs an object containing at least one virtual function. |
| 106 | + * - a node which represents a derived-to-base instruction that converts from `c`. |
87 | 107 | */ |
88 | | -private module VirtualDispatch { |
89 | | - /** A call that may dispatch differently depending on the qualifier value. */ |
90 | | - abstract class DataSensitiveCall extends DataFlowCall { |
91 | | - /** |
92 | | - * Gets the node whose value determines the target of this call. This node |
93 | | - * could be the qualifier of a virtual dispatch or the function-pointer |
94 | | - * expression in a call to a function pointer. What they have in common is |
95 | | - * that we need to find out which data flows there, and then it's up to the |
96 | | - * `resolve` predicate to stitch that information together and resolve the |
97 | | - * call. |
98 | | - */ |
99 | | - abstract Node getDispatchValue(); |
100 | | - |
101 | | - /** Gets a candidate target for this call. */ |
102 | | - abstract Function resolve(); |
103 | | - |
104 | | - /** |
105 | | - * Whether `src` can flow to this call. |
106 | | - * |
107 | | - * Searches backwards from `getDispatchValue()` to `src`. The `allowFromArg` |
108 | | - * parameter is true when the search is allowed to continue backwards into |
109 | | - * a parameter; non-recursive callers should pass `_` for `allowFromArg`. |
110 | | - */ |
111 | | - predicate flowsFrom(Node src, boolean allowFromArg) { |
112 | | - src = this.getDispatchValue() and allowFromArg = true |
113 | | - or |
114 | | - exists(Node other, boolean allowOtherFromArg | this.flowsFrom(other, allowOtherFromArg) | |
115 | | - // Call argument |
116 | | - exists(DataFlowCall call, Position i | |
117 | | - other.(ParameterNode).isParameterOf(pragma[only_bind_into](call).getStaticCallTarget(), i) and |
118 | | - src.(ArgumentNode).argumentOf(call, pragma[only_bind_into](pragma[only_bind_out](i))) |
119 | | - ) and |
120 | | - allowOtherFromArg = true and |
121 | | - allowFromArg = true |
| 108 | +private predicate lambdaSourceImpl(RelevantNode n, Class c) { |
| 109 | + // Object construction |
| 110 | + exists(CallInstruction call, ThisArgumentOperand qualifier, Call e | |
| 111 | + qualifier = call.getThisArgumentOperand() and |
| 112 | + n.(PostUpdateNode).getPreUpdateNode().asOperand() = qualifier and |
| 113 | + call.getStaticCallTarget() instanceof Constructor and |
| 114 | + qualifier.getType().stripType() = c and |
| 115 | + c.getABaseClass*().getAMemberFunction().isVirtual() and |
| 116 | + e = call.getUnconvertedResultExpression() and |
| 117 | + not ignoreConstructor(e) |
| 118 | + | |
| 119 | + exists(c.getABaseClass()) |
| 120 | + or |
| 121 | + exists(c.getADerivedClass()) |
| 122 | + ) |
| 123 | + or |
| 124 | + // Conversion to a base class |
| 125 | + exists(ConvertToBaseInstruction convert | |
| 126 | + // Only keep the most specific cast |
| 127 | + not convert.getUnary() instanceof ConvertToBaseInstruction and |
| 128 | + n.asInstruction() = convert and |
| 129 | + convert.getDerivedClass() = c and |
| 130 | + c.getABaseClass*().getAMemberFunction().isVirtual() |
| 131 | + ) |
| 132 | +} |
| 133 | + |
| 134 | +private module TrackVirtualDispatch<methodDispatchSig/1 lambdaDispatch0> { |
| 135 | + /** |
| 136 | + * Gets a possible runtime target of `c` using both static call-target |
| 137 | + * information, and call-target resolution from `lambdaDispatch0`. |
| 138 | + */ |
| 139 | + private DataFlowPrivate::DataFlowCallable dispatch(DataFlowPrivate::DataFlowCall c) { |
| 140 | + result = nonVirtualDispatch(c) or |
| 141 | + result = lambdaDispatch0(c) |
| 142 | + } |
| 143 | + |
| 144 | + private module TtInput implements TypeTrackingInput<Location> { |
| 145 | + final class Node = RelevantNode; |
| 146 | + |
| 147 | + class LocalSourceNode extends Node { |
| 148 | + LocalSourceNode() { |
| 149 | + this instanceof ParameterNode |
122 | 150 | or |
123 | | - // Call return |
124 | | - exists(DataFlowCall call, ReturnKind returnKind | |
125 | | - other = getAnOutNode(call, returnKind) and |
126 | | - returnNodeWithKindAndEnclosingCallable(src, returnKind, call.getStaticCallTarget()) |
127 | | - ) and |
128 | | - allowFromArg = false |
| 151 | + this instanceof DataFlowPrivate::OutNode |
129 | 152 | or |
130 | | - // Local flow |
131 | | - localFlowStep(src, other) and |
132 | | - allowFromArg = allowOtherFromArg |
| 153 | + DataFlowPrivate::readStep(_, _, this) |
133 | 154 | or |
134 | | - // Flow from global variable to load. |
135 | | - exists(LoadInstruction load, GlobalOrNamespaceVariable var | |
136 | | - var = src.asVariable() and |
137 | | - other.asInstruction() = load and |
138 | | - addressOfGlobal(load.getSourceAddress(), var) and |
139 | | - // The `allowFromArg` concept doesn't play a role when `src` is a |
140 | | - // global variable, so we just set it to a single arbitrary value for |
141 | | - // performance. |
142 | | - allowFromArg = true |
143 | | - ) |
| 155 | + DataFlowPrivate::storeStep(_, _, this) |
| 156 | + or |
| 157 | + DataFlowPrivate::jumpStep(_, this) |
144 | 158 | or |
145 | | - // Flow from store to global variable. |
146 | | - exists(StoreInstruction store, GlobalOrNamespaceVariable var | |
147 | | - var = other.asVariable() and |
148 | | - store = src.asInstruction() and |
149 | | - storeIntoGlobal(store, var) and |
150 | | - // Setting `allowFromArg` to `true` like in the base case means we |
151 | | - // treat a store to a global variable like the dispatch itself: flow |
152 | | - // may come from anywhere. |
153 | | - allowFromArg = true |
| 159 | + lambdaSourceImpl(this, _) |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + final private class ContentSetFinal = ContentSet; |
| 164 | + |
| 165 | + class Content extends ContentSetFinal { |
| 166 | + Content() { |
| 167 | + exists(DataFlow::Content c | |
| 168 | + this.isSingleton(c) and |
| 169 | + c.getIndirectionIndex() = 1 |
154 | 170 | ) |
155 | | - ) |
| 171 | + } |
156 | 172 | } |
157 | | - } |
158 | 173 |
|
159 | | - pragma[noinline] |
160 | | - private predicate storeIntoGlobal(StoreInstruction store, GlobalOrNamespaceVariable var) { |
161 | | - addressOfGlobal(store.getDestinationAddress(), var) |
162 | | - } |
| 174 | + class ContentFilter extends Content { |
| 175 | + Content getAMatchingContent() { result = this } |
| 176 | + } |
163 | 177 |
|
164 | | - /** Holds if `addressInstr` is an instruction that produces the address of `var`. */ |
165 | | - private predicate addressOfGlobal(Instruction addressInstr, GlobalOrNamespaceVariable var) { |
166 | | - // Access directly to the global variable |
167 | | - addressInstr.(VariableAddressInstruction).getAstVariable() = var |
168 | | - or |
169 | | - // Access to a field on a global union |
170 | | - exists(FieldAddressInstruction fa | |
171 | | - fa = addressInstr and |
172 | | - fa.getObjectAddress().(VariableAddressInstruction).getAstVariable() = var and |
173 | | - fa.getField().getDeclaringType() instanceof Union |
174 | | - ) |
175 | | - } |
| 178 | + predicate compatibleContents(Content storeContents, Content loadContents) { |
| 179 | + storeContents = loadContents |
| 180 | + } |
176 | 181 |
|
177 | | - /** |
178 | | - * A ReturnNode with its ReturnKind and its enclosing callable. |
179 | | - * |
180 | | - * Used to fix a join ordering issue in flowsFrom. |
181 | | - */ |
182 | | - pragma[noinline] |
183 | | - private predicate returnNodeWithKindAndEnclosingCallable( |
184 | | - ReturnNode node, ReturnKind kind, DataFlowCallable callable |
185 | | - ) { |
186 | | - node.getKind() = kind and |
187 | | - node.getFunction() = callable.getUnderlyingCallable() |
188 | | - } |
| 182 | + predicate simpleLocalSmallStep(Node nodeFrom, Node nodeTo) { |
| 183 | + nodeFrom.getFunction() instanceof Function and |
| 184 | + simpleLocalFlowStep(nodeFrom, nodeTo, _) |
| 185 | + } |
189 | 186 |
|
190 | | - /** Call through a function pointer. */ |
191 | | - private class DataSensitiveExprCall extends DataSensitiveCall { |
192 | | - DataSensitiveExprCall() { not exists(this.getStaticCallTarget()) } |
| 187 | + predicate levelStepNoCall(Node n1, LocalSourceNode n2) { none() } |
193 | 188 |
|
194 | | - override Node getDispatchValue() { result.asOperand() = this.getCallTargetOperand() } |
| 189 | + predicate levelStepCall(Node n1, LocalSourceNode n2) { none() } |
195 | 190 |
|
196 | | - override Function resolve() { |
197 | | - exists(FunctionInstruction fi | |
198 | | - this.flowsFrom(instructionNode(fi), _) and |
199 | | - result = fi.getFunctionSymbol() |
200 | | - ) and |
201 | | - ( |
202 | | - this.getNumberOfArguments() <= result.getEffectiveNumberOfParameters() and |
203 | | - this.getNumberOfArguments() >= result.getEffectiveNumberOfParameters() |
204 | | - or |
205 | | - result.isVarargs() |
206 | | - ) |
207 | | - } |
208 | | - } |
| 191 | + predicate storeStep(Node n1, Node n2, Content f) { DataFlowPrivate::storeStep(n1, f, n2) } |
209 | 192 |
|
210 | | - /** Call to a virtual function. */ |
211 | | - private class DataSensitiveOverriddenFunctionCall extends DataSensitiveCall { |
212 | | - DataSensitiveOverriddenFunctionCall() { |
213 | | - exists( |
214 | | - this.getStaticCallTarget() |
215 | | - .getUnderlyingCallable() |
216 | | - .(VirtualFunction) |
217 | | - .getAnOverridingFunction() |
| 193 | + predicate callStep(Node n1, LocalSourceNode n2) { |
| 194 | + exists(DataFlowPrivate::DataFlowCall call, DataFlowPrivate::Position pos | |
| 195 | + n1.(DataFlowPrivate::ArgumentNode).argumentOf(call, pos) and |
| 196 | + n2.(ParameterNode).isParameterOf(dispatch(call), pos) |
218 | 197 | ) |
219 | 198 | } |
220 | 199 |
|
221 | | - override Node getDispatchValue() { result.asInstruction() = this.getArgument(-1) } |
222 | | - |
223 | | - override MemberFunction resolve() { |
224 | | - exists(Class overridingClass | |
225 | | - this.overrideMayAffectCall(overridingClass, result) and |
226 | | - this.hasFlowFromCastFrom(overridingClass) |
| 200 | + predicate returnStep(Node n1, LocalSourceNode n2) { |
| 201 | + exists(DataFlowPrivate::DataFlowCallable callable, DataFlowPrivate::DataFlowCall call | |
| 202 | + n1.(DataFlowPrivate::ReturnNode).getEnclosingCallable() = callable and |
| 203 | + callable = dispatch(call) and |
| 204 | + n2 = DataFlowPrivate::getAnOutNode(call, n1.(DataFlowPrivate::ReturnNode).getKind()) |
227 | 205 | ) |
228 | 206 | } |
229 | 207 |
|
230 | | - /** |
231 | | - * Holds if `this` is a virtual function call whose static target is |
232 | | - * overridden by `overridingFunction` in `overridingClass`. |
233 | | - */ |
234 | | - pragma[noinline] |
235 | | - private predicate overrideMayAffectCall(Class overridingClass, MemberFunction overridingFunction) { |
236 | | - overridingFunction.getAnOverriddenFunction+() = |
237 | | - this.getStaticCallTarget().getUnderlyingCallable().(VirtualFunction) and |
238 | | - overridingFunction.getDeclaringType() = overridingClass |
| 208 | + predicate loadStep(Node n1, LocalSourceNode n2, Content f) { |
| 209 | + DataFlowPrivate::readStep(n1, f, n2) |
239 | 210 | } |
240 | 211 |
|
241 | | - /** |
242 | | - * Holds if the qualifier of `this` has flow from an upcast from |
243 | | - * `derivedClass`. |
244 | | - */ |
245 | | - pragma[noinline] |
246 | | - private predicate hasFlowFromCastFrom(Class derivedClass) { |
247 | | - exists(ConvertToBaseInstruction toBase | |
248 | | - this.flowsFrom(instructionNode(toBase), _) and |
249 | | - derivedClass = toBase.getDerivedClass() |
250 | | - ) |
251 | | - } |
| 212 | + predicate loadStoreStep(Node nodeFrom, Node nodeTo, Content f1, Content f2) { none() } |
| 213 | + |
| 214 | + predicate withContentStep(Node nodeFrom, LocalSourceNode nodeTo, ContentFilter f) { none() } |
| 215 | + |
| 216 | + predicate withoutContentStep(Node nodeFrom, LocalSourceNode nodeTo, ContentFilter f) { none() } |
| 217 | + |
| 218 | + predicate jumpStep(Node n1, LocalSourceNode n2) { DataFlowPrivate::jumpStep(n1, n2) } |
| 219 | + |
| 220 | + predicate hasFeatureBacktrackStoreTarget() { none() } |
| 221 | + } |
| 222 | + |
| 223 | + private predicate lambdaSource(RelevantNode n) { lambdaSourceImpl(n, _) } |
| 224 | + |
| 225 | + /** |
| 226 | + * Holds if `n` is the qualifier of `call` which targets the virtual member |
| 227 | + * function `mf`. |
| 228 | + */ |
| 229 | + private predicate lambdaSinkImpl(RelevantNode n, CallInstruction call, MemberFunction mf) { |
| 230 | + n.asOperand() = call.getThisArgumentOperand() and |
| 231 | + call.getStaticCallTarget() = mf and |
| 232 | + mf.isVirtual() |
| 233 | + } |
| 234 | + |
| 235 | + private predicate lambdaSink(RelevantNode n) { lambdaSinkImpl(n, _, _) } |
| 236 | + |
| 237 | + private import TypeTracking<Location, TtInput>::TypeTrack<lambdaSource/1>::Graph<lambdaSink/1> |
| 238 | + |
| 239 | + private predicate edgePlus(PathNode n1, PathNode n2) = fastTC(edges/2)(n1, n2) |
| 240 | + |
| 241 | + /** |
| 242 | + * Gets the most specific implementation of `mf` that may be called when the |
| 243 | + * qualifier has runtime type `c`. |
| 244 | + */ |
| 245 | + private MemberFunction mostSpecific(MemberFunction mf, Class c) { |
| 246 | + lambdaSinkImpl(_, _, mf) and |
| 247 | + mf.getAnOverridingFunction*() = result and |
| 248 | + ( |
| 249 | + result.getDeclaringType() = c |
| 250 | + or |
| 251 | + not c.getAMemberFunction().getAnOverriddenFunction*() = mf and |
| 252 | + result = mostSpecific(mf, c.getABaseClass()) |
| 253 | + ) |
| 254 | + } |
| 255 | + |
| 256 | + /** |
| 257 | + * Gets a possible pair of end-points `(p1, p2)` where: |
| 258 | + * - `p1` is a derived-to-base conversion that converts from some |
| 259 | + * class `derived`, and |
| 260 | + * - `p2` is the qualifier of a call to a virtual function that may |
| 261 | + * target `callable`, and |
| 262 | + * - `callable` is the most specific implementation that may be called when |
| 263 | + * the qualifier has type `derived`. |
| 264 | + */ |
| 265 | + private predicate pairCand( |
| 266 | + PathNode p1, PathNode p2, DataFlowPrivate::DataFlowCallable callable, |
| 267 | + DataFlowPrivate::DataFlowCall call |
| 268 | + ) { |
| 269 | + exists(Class derived, MemberFunction mf | |
| 270 | + lambdaSourceImpl(p1.getNode(), derived) and |
| 271 | + lambdaSinkImpl(p2.getNode(), call.asCallInstruction(), mf) and |
| 272 | + p1.isSource() and |
| 273 | + p2.isSink() and |
| 274 | + callable.asSourceCallable() = mostSpecific(mf, derived) |
| 275 | + ) |
| 276 | + } |
| 277 | + |
| 278 | + /** Gets a possible run-time target of `call`. */ |
| 279 | + DataFlowPrivate::DataFlowCallable lambdaDispatch(DataFlowPrivate::DataFlowCall call) { |
| 280 | + exists(PathNode p1, PathNode p2 | p1 = p2 or edgePlus(p1, p2) | pairCand(p1, p2, result, call)) |
252 | 281 | } |
253 | 282 | } |
254 | 283 |
|
|
0 commit comments