Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 49008c9

Browse files
committed
C++: IR data flow local virtual dispatch
This is just good enough to cause no performance regressions and pass the virtual-dispatch tests we have for `security.TaintTracking`. In particular, it fixes the tests for `UncontrolledProcessOperation.ql` when enabling `DefaultTaintTracking.qll`.
1 parent 2bcd418 commit 49008c9

5 files changed

Lines changed: 115 additions & 0 deletions

File tree

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
private import cpp
22
private import semmle.code.cpp.ir.IR
3+
private import semmle.code.cpp.ir.dataflow.DataFlow
34

45
Function viableImpl(CallInstruction call) { result = viableCallable(call) }
56

@@ -20,6 +21,58 @@ Function viableCallable(CallInstruction call) {
2021
functionSignatureWithBody(qualifiedName, nparams, result) and
2122
strictcount(Function other | functionSignatureWithBody(qualifiedName, nparams, other)) = 1
2223
)
24+
or
25+
// Rudimentary virtual dispatch support. It's essentially local data flow
26+
// where the source is a derived-to-base conversion and the target is the
27+
// qualifier of a call.
28+
exists(Class derived, DataFlow::Node thisArgument |
29+
nodeMayHaveClass(derived, thisArgument) and
30+
overrideMayAffectCall(derived, thisArgument, _, result, call)
31+
)
32+
}
33+
34+
/**
35+
* Holds if `call` is a virtual function call with qualifier `thisArgument` in
36+
* `enclosingFunction`, whose static target is overridden by
37+
* `overridingFunction` in `overridingClass`.
38+
*/
39+
pragma[noinline]
40+
private predicate overrideMayAffectCall(
41+
Class overridingClass, DataFlow::Node thisArgument, Function enclosingFunction,
42+
MemberFunction overridingFunction, CallInstruction call
43+
) {
44+
call.getEnclosingFunction() = enclosingFunction and
45+
overridingFunction.getAnOverriddenFunction+() = call.getStaticCallTarget() and
46+
overridingFunction.getDeclaringType() = overridingClass and
47+
thisArgument = DataFlow::instructionNode(call.getThisArgument())
48+
}
49+
50+
/**
51+
* Holds if `node` may have dynamic class `derived`, where `derived` is a class
52+
* that may affect virtual dispatch within the enclosing function.
53+
*
54+
* For the sake of performance, this recursion is written out manually to make
55+
* it a relation on `Class x Node` rather than `Node x Node` or `MemberFunction
56+
* x Node`, both of which would be larger. It's a forward search since there
57+
* should usually be fewer classes than calls.
58+
*
59+
* If a value is cast several classes up in the hierarchy, that will be modeled
60+
* as a chain of `ConvertToBaseInstruction`s and will cause the search to start
61+
* from each of them and pass through subsequent ones. There might be
62+
* performance to gain by stopping before a second upcast and reconstructing
63+
* the full chain in a "big-step" recursion after this one.
64+
*/
65+
private predicate nodeMayHaveClass(Class derived, DataFlow::Node node) {
66+
exists(ConvertToBaseInstruction toBase |
67+
derived = toBase.getDerivedClass() and
68+
overrideMayAffectCall(derived, _, toBase.getEnclosingFunction(), _, _) and
69+
node.asInstruction() = toBase
70+
)
71+
or
72+
exists(DataFlow::Node prev |
73+
nodeMayHaveClass(derived, prev) and
74+
DataFlow::localFlowStep(prev, node)
75+
)
2376
}
2477

2578
/**
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
int source();
2+
void sink(int);
3+
4+
// This class has the opposite behavior of what the member function names suggest.
5+
struct Top {
6+
virtual int isSource1() { return 0; }
7+
virtual int isSource2() { return 0; }
8+
virtual void isSink(int x) { }
9+
virtual int notSource1() { return source(); }
10+
virtual int notSource2() { return source(); }
11+
virtual void notSink(int x) { sink(x); }
12+
};
13+
14+
// This class has the correct behavior for just the functions ending in 2.
15+
struct Middle : Top {
16+
int isSource2() override { return source(); }
17+
int notSource2() override { return 0; }
18+
};
19+
20+
// This class has all the behavior suggested by the function names.
21+
struct Bottom : Middle {
22+
int isSource1() override { return source(); }
23+
void isSink(int x) override { sink(x); }
24+
int notSource1() override { return 0; }
25+
void notSink(int x) override { }
26+
};
27+
28+
void VirtualDispatch(Bottom *bottomPtr, Bottom &bottomRef) {
29+
Top *topPtr = bottomPtr, &topRef = bottomRef;
30+
31+
sink(topPtr->isSource1()); // flow [NOT DETECTED]
32+
sink(topPtr->isSource2()); // flow [NOT DETECTED by AST]
33+
topPtr->isSink(source()); // flow [NOT DETECTED]
34+
35+
sink(topPtr->notSource1()); // no flow [FALSE POSITIVE]
36+
sink(topPtr->notSource2()); // no flow [FALSE POSITIVE]
37+
topPtr->notSink(source()); // no flow [FALSE POSITIVE]
38+
39+
sink(topRef.isSource1()); // flow [NOT DETECTED]
40+
sink(topRef.isSource2()); // flow [NOT DETECTED by AST]
41+
topRef.isSink(source()); // flow [NOT DETECTED]
42+
43+
sink(topRef.notSource1()); // no flow [FALSE POSITIVE]
44+
sink(topRef.notSource2()); // no flow [FALSE POSITIVE]
45+
topRef.notSink(source()); // no flow [FALSE POSITIVE]
46+
}

cpp/ql/test/library-tests/dataflow/dataflow-tests/test.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
| clang.cpp:30:27:30:34 | call to getFirst | clang.cpp:28:27:28:32 | call to source |
1616
| clang.cpp:37:10:37:11 | m2 | clang.cpp:34:32:34:37 | call to source |
1717
| clang.cpp:45:17:45:18 | m2 | clang.cpp:43:35:43:40 | call to source |
18+
| dispatch.cpp:11:38:11:38 | x | dispatch.cpp:37:19:37:24 | call to source |
19+
| dispatch.cpp:11:38:11:38 | x | dispatch.cpp:45:18:45:23 | call to source |
20+
| dispatch.cpp:35:16:35:25 | call to notSource1 | dispatch.cpp:9:37:9:42 | call to source |
21+
| dispatch.cpp:36:16:36:25 | call to notSource2 | dispatch.cpp:10:37:10:42 | call to source |
22+
| dispatch.cpp:43:15:43:24 | call to notSource1 | dispatch.cpp:9:37:9:42 | call to source |
23+
| dispatch.cpp:44:15:44:24 | call to notSource2 | dispatch.cpp:10:37:10:42 | call to source |
1824
| lambdas.cpp:14:3:14:6 | t | lambdas.cpp:8:10:8:15 | call to source |
1925
| lambdas.cpp:18:8:18:8 | call to operator() | lambdas.cpp:8:10:8:15 | call to source |
2026
| lambdas.cpp:21:3:21:6 | t | lambdas.cpp:8:10:8:15 | call to source |

cpp/ql/test/library-tests/dataflow/dataflow-tests/test_diff.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
| clang.cpp:28:27:28:32 | clang.cpp:29:27:29:28 | AST only |
66
| clang.cpp:28:27:28:32 | clang.cpp:30:27:30:34 | AST only |
77
| clang.cpp:39:42:39:47 | clang.cpp:41:18:41:19 | IR only |
8+
| dispatch.cpp:16:37:16:42 | dispatch.cpp:32:16:32:24 | IR only |
9+
| dispatch.cpp:16:37:16:42 | dispatch.cpp:40:15:40:23 | IR only |
810
| lambdas.cpp:8:10:8:15 | lambdas.cpp:14:3:14:6 | AST only |
911
| lambdas.cpp:8:10:8:15 | lambdas.cpp:18:8:18:8 | AST only |
1012
| lambdas.cpp:8:10:8:15 | lambdas.cpp:21:3:21:6 | AST only |

cpp/ql/test/library-tests/dataflow/dataflow-tests/test_ir.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212
| clang.cpp:37:10:37:11 | Load: m2 | clang.cpp:34:32:34:37 | Call: call to source |
1313
| clang.cpp:41:18:41:19 | Load: m2 | clang.cpp:39:42:39:47 | Call: call to source |
1414
| clang.cpp:45:17:45:18 | Load: m2 | clang.cpp:43:35:43:40 | Call: call to source |
15+
| dispatch.cpp:11:38:11:38 | Load: x | dispatch.cpp:37:19:37:24 | Call: call to source |
16+
| dispatch.cpp:11:38:11:38 | Load: x | dispatch.cpp:45:18:45:23 | Call: call to source |
17+
| dispatch.cpp:32:16:32:24 | Call: call to isSource2 | dispatch.cpp:16:37:16:42 | Call: call to source |
18+
| dispatch.cpp:35:16:35:25 | Call: call to notSource1 | dispatch.cpp:9:37:9:42 | Call: call to source |
19+
| dispatch.cpp:36:16:36:25 | Call: call to notSource2 | dispatch.cpp:10:37:10:42 | Call: call to source |
20+
| dispatch.cpp:40:15:40:23 | Call: call to isSource2 | dispatch.cpp:16:37:16:42 | Call: call to source |
21+
| dispatch.cpp:43:15:43:24 | Call: call to notSource1 | dispatch.cpp:9:37:9:42 | Call: call to source |
22+
| dispatch.cpp:44:15:44:24 | Call: call to notSource2 | dispatch.cpp:10:37:10:42 | Call: call to source |
1523
| test.cpp:7:8:7:9 | Load: t1 | test.cpp:6:12:6:17 | Call: call to source |
1624
| test.cpp:9:8:9:9 | Load: t1 | test.cpp:6:12:6:17 | Call: call to source |
1725
| test.cpp:10:8:10:9 | Load: t2 | test.cpp:6:12:6:17 | Call: call to source |

0 commit comments

Comments
 (0)