Skip to content

Commit d4188d5

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

File tree

1 file changed

+174
-145
lines changed

1 file changed

+174
-145
lines changed

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

Lines changed: 174 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,200 @@ 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 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`.
87107
*/
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
122150
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
129152
or
130-
// Local flow
131-
localFlowStep(src, other) and
132-
allowFromArg = allowOtherFromArg
153+
DataFlowPrivate::readStep(_, _, this)
133154
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)
144158
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
154170
)
155-
)
171+
}
156172
}
157-
}
158173

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+
}
163177

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+
}
176181

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+
}
189186

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() }
193188

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

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) }
209192

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)
218197
)
219198
}
220199

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())
227205
)
228206
}
229207

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)
239210
}
240211

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))
252281
}
253282
}
254283

0 commit comments

Comments
 (0)