Skip to content

Commit ec233de

Browse files
committed
C++: Instantiate the type tracking module inside a reusable module like it's done in Java.
1 parent caf7464 commit ec233de

File tree

1 file changed

+172
-145
lines changed

1 file changed

+172
-145
lines changed

cpp/ql/lib/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll

Lines changed: 172 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
private import cpp
22
private import semmle.code.cpp.ir.IR
3+
private import semmle.code.cpp.ir.dataflow.DataFlow
34
private import DataFlowPrivate as DataFlowPrivate
45
private import DataFlowUtil
56
private import DataFlowImplCommon as DataFlowImplCommon
7+
private import codeql.typetracking.TypeTracking
8+
private import SsaImpl as SsaImpl
69

710
/**
811
* Holds if `f` has name `qualifiedName` and `nparams` parameter count. This is
@@ -81,174 +84,198 @@ private DataFlowPrivate::DataFlowCallable nonVirtualDispatch(DataFlowPrivate::Da
8184
.viableTarget(call.asCallInstruction().getUnconvertedResultExpression())
8285
}
8386

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+
84102
/**
85-
* Provides virtual dispatch support compatible with the original
86-
* implementation of `semmle.code.cpp.security.TaintTracking`.
103+
* Holds if `n` is a node which represents a derived-to-base instruction
104+
* that converts from `derived`.
87105
*/
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
106+
private predicate lambdaSourceImpl(RelevantNode n, Class c) {
107+
// Object construction
108+
exists(CallInstruction call, ThisArgumentOperand qualifier, Call e |
109+
qualifier = call.getThisArgumentOperand() and
110+
n.(PostUpdateNode).getPreUpdateNode().asOperand() = qualifier and
111+
call.getStaticCallTarget() instanceof Constructor and
112+
qualifier.getType().stripType() = c and
113+
c.getABaseClass*().getAMemberFunction().isVirtual() and
114+
e = call.getUnconvertedResultExpression() and
115+
not ignoreConstructor(e)
116+
|
117+
exists(c.getABaseClass())
118+
or
119+
exists(c.getADerivedClass())
120+
)
121+
or
122+
// Conversion to a base class
123+
exists(ConvertToBaseInstruction convert |
124+
// Only keep the most specific cast
125+
not convert.getUnary() instanceof ConvertToBaseInstruction and
126+
n.asInstruction() = convert and
127+
convert.getDerivedClass() = c and
128+
c.getABaseClass*().getAMemberFunction().isVirtual()
129+
)
130+
}
131+
132+
private module TrackVirtualDispatch<methodDispatchSig/1 lambdaDispatch0> {
133+
/**
134+
* Gets a possible runtime target of `c` using both static call-target
135+
* information, and call-target resolution from `lambdaDispatch0`.
136+
*/
137+
private DataFlowPrivate::DataFlowCallable dispatch(DataFlowPrivate::DataFlowCall c) {
138+
result = nonVirtualDispatch(c) or
139+
result = lambdaDispatch0(c)
140+
}
141+
142+
private module TtInput implements TypeTrackingInput<Location> {
143+
final class Node = RelevantNode;
144+
145+
class LocalSourceNode extends Node {
146+
LocalSourceNode() {
147+
this instanceof ParameterNode
122148
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
149+
this instanceof DataFlowPrivate::OutNode
129150
or
130-
// Local flow
131-
localFlowStep(src, other) and
132-
allowFromArg = allowOtherFromArg
151+
DataFlowPrivate::readStep(_, _, this)
133152
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-
)
153+
DataFlowPrivate::storeStep(_, _, this)
154+
or
155+
DataFlowPrivate::jumpStep(_, this)
144156
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
157+
lambdaSourceImpl(this, _)
158+
}
159+
}
160+
161+
final private class ContentSetFinal = ContentSet;
162+
163+
class Content extends ContentSetFinal {
164+
Content() {
165+
exists(DataFlow::Content c |
166+
this.isSingleton(c) and
167+
c.getIndirectionIndex() = 1
154168
)
155-
)
169+
}
156170
}
157-
}
158171

159-
pragma[noinline]
160-
private predicate storeIntoGlobal(StoreInstruction store, GlobalOrNamespaceVariable var) {
161-
addressOfGlobal(store.getDestinationAddress(), var)
162-
}
172+
class ContentFilter extends Content {
173+
Content getAMatchingContent() { result = this }
174+
}
163175

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-
}
176+
predicate compatibleContents(Content storeContents, Content loadContents) {
177+
storeContents = loadContents
178+
}
176179

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-
}
180+
predicate simpleLocalSmallStep(Node nodeFrom, Node nodeTo) {
181+
nodeFrom.getFunction() instanceof Function and
182+
simpleLocalFlowStep(nodeFrom, nodeTo, _)
183+
}
189184

190-
/** Call through a function pointer. */
191-
private class DataSensitiveExprCall extends DataSensitiveCall {
192-
DataSensitiveExprCall() { not exists(this.getStaticCallTarget()) }
185+
predicate levelStepNoCall(Node n1, LocalSourceNode n2) { none() }
193186

194-
override Node getDispatchValue() { result.asOperand() = this.getCallTargetOperand() }
187+
predicate levelStepCall(Node n1, LocalSourceNode n2) { none() }
195188

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-
}
189+
predicate storeStep(Node n1, Node n2, Content f) { DataFlowPrivate::storeStep(n1, f, n2) }
209190

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()
191+
predicate callStep(Node n1, LocalSourceNode n2) {
192+
exists(DataFlowPrivate::DataFlowCall call, DataFlowPrivate::Position pos |
193+
n1.(DataFlowPrivate::ArgumentNode).argumentOf(call, pos) and
194+
n2.(ParameterNode).isParameterOf(dispatch(call), pos)
218195
)
219196
}
220197

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)
198+
predicate returnStep(Node n1, LocalSourceNode n2) {
199+
exists(DataFlowPrivate::DataFlowCallable callable, DataFlowPrivate::DataFlowCall call |
200+
n1.(DataFlowPrivate::ReturnNode).getEnclosingCallable() = callable and
201+
callable = dispatch(call) and
202+
n2 = DataFlowPrivate::getAnOutNode(call, n1.(DataFlowPrivate::ReturnNode).getKind())
227203
)
228204
}
229205

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
206+
predicate loadStep(Node n1, LocalSourceNode n2, Content f) {
207+
DataFlowPrivate::readStep(n1, f, n2)
239208
}
240209

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-
}
210+
predicate loadStoreStep(Node nodeFrom, Node nodeTo, Content f1, Content f2) { none() }
211+
212+
predicate withContentStep(Node nodeFrom, LocalSourceNode nodeTo, ContentFilter f) { none() }
213+
214+
predicate withoutContentStep(Node nodeFrom, LocalSourceNode nodeTo, ContentFilter f) { none() }
215+
216+
predicate jumpStep(Node n1, LocalSourceNode n2) { DataFlowPrivate::jumpStep(n1, n2) }
217+
218+
predicate hasFeatureBacktrackStoreTarget() { none() }
219+
}
220+
221+
private predicate lambdaSource(RelevantNode n) { lambdaSourceImpl(n, _) }
222+
223+
/**
224+
* Holds if `n` is the qualifier of `call` which targets the virtual member
225+
* function `mf`.
226+
*/
227+
private predicate lambdaSinkImpl(RelevantNode n, CallInstruction call, MemberFunction mf) {
228+
n.asOperand() = call.getThisArgumentOperand() and
229+
call.getStaticCallTarget() = mf and
230+
mf.isVirtual()
231+
}
232+
233+
private predicate lambdaSink(RelevantNode n) { lambdaSinkImpl(n, _, _) }
234+
235+
private import TypeTracking<Location, TtInput>::TypeTrack<lambdaSource/1>::Graph<lambdaSink/1>
236+
237+
private predicate edgePlus(PathNode n1, PathNode n2) = fastTC(edges/2)(n1, n2)
238+
239+
/**
240+
* Gets the most specific implementation of `mf` that may be called when the
241+
* qualifier has runtime type `c`.
242+
*/
243+
private MemberFunction mostSpecific(MemberFunction mf, Class c) {
244+
lambdaSinkImpl(_, _, mf) and
245+
mf.getAnOverridingFunction*() = result and
246+
(
247+
result.getDeclaringType() = c
248+
or
249+
not c.getAMemberFunction().getAnOverriddenFunction*() = mf and
250+
result = mostSpecific(mf, c.getABaseClass())
251+
)
252+
}
253+
254+
/**
255+
* Gets a possible pair of end-points `(p1, p2)` where:
256+
* - `p1` is a derived-to-base conversion that converts from some
257+
* class `derived`, and
258+
* - `p2` is the qualifier of a call to a virtual function that may
259+
* target `callable`, and
260+
* - `callable` is the most specific implementation that may be called when
261+
* the qualifier has type `derived`.
262+
*/
263+
private predicate pairCand(
264+
PathNode p1, PathNode p2, DataFlowPrivate::DataFlowCallable callable,
265+
DataFlowPrivate::DataFlowCall call
266+
) {
267+
exists(Class derived, MemberFunction mf |
268+
lambdaSourceImpl(p1.getNode(), derived) and
269+
lambdaSinkImpl(p2.getNode(), call.asCallInstruction(), mf) and
270+
p1.isSource() and
271+
p2.isSink() and
272+
callable.asSourceCallable() = mostSpecific(mf, derived)
273+
)
274+
}
275+
276+
/** Gets a possible run-time target of `call`. */
277+
DataFlowPrivate::DataFlowCallable lambdaDispatch(DataFlowPrivate::DataFlowCall call) {
278+
exists(PathNode p1, PathNode p2 | p1 = p2 or edgePlus(p1, p2) | pairCand(p1, p2, result, call))
252279
}
253280
}
254281

0 commit comments

Comments
 (0)