Skip to content

Commit 8c5b336

Browse files
committed
C++: Make the two must-flow queries use the new must-flow library
1 parent ee9c0dc commit 8c5b336

File tree

2 files changed

+85
-297
lines changed

2 files changed

+85
-297
lines changed

cpp/ql/src/Likely Bugs/Memory Management/ReturnStackAllocatedMemory.ql

Lines changed: 51 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -18,157 +18,70 @@ import cpp
1818
// recomputing the IR.
1919
private import semmle.code.cpp.valuenumbering.GlobalValueNumbering
2020
import semmle.code.cpp.ir.IR
21-
import semmle.code.cpp.ir.dataflow.DataFlow::DataFlow
21+
import semmle.code.cpp.ir.dataflow.MustFlow
22+
import PathGraph
2223

2324
/** Holds if `f` has a name that we intrepret as evidence of intentionally returning the value of the stack pointer. */
2425
predicate intentionallyReturnsStackPointer(Function f) {
2526
f.getName().toLowerCase().matches(["%stack%", "%sp%"])
2627
}
2728

28-
/**
29-
* Holds if `source` is a node that represents the use of a stack variable
30-
*/
31-
predicate isSource(Node source) {
32-
exists(VariableAddressInstruction var, Function func |
33-
var = source.asInstruction() and
34-
func = var.getEnclosingFunction() and
35-
var.getASTVariable() instanceof StackVariable and
36-
// Pointer-to-member types aren't properly handled in the dbscheme.
37-
not var.getResultType() instanceof PointerToMemberType and
38-
// Rule out FPs caused by extraction errors.
39-
not any(ErrorExpr e).getEnclosingFunction() = func and
40-
not intentionallyReturnsStackPointer(func)
41-
)
42-
}
43-
44-
/**
45-
* Holds if `sink` is a node that represents the `StoreInstruction` that is subsequently used in
46-
* a `ReturnValueInstruction`. We use the `StoreInstruction` instead of the instruction that defines the
47-
* `ReturnValueInstruction`'s source value oprand because the former has better location information.
48-
*/
49-
predicate isSink(Node sink) {
50-
exists(StoreInstruction store |
51-
store.getDestinationAddress().(VariableAddressInstruction).getIRVariable() instanceof
52-
IRReturnVariable and
53-
sink.asOperand() = store.getSourceValueOperand()
54-
)
55-
}
56-
57-
/** Holds if `node1` _must_ flow to `node2`. */
58-
predicate step(Node node1, Node node2) {
59-
instructionToOperandStep(node1.asInstruction(), node2.asOperand())
60-
or
61-
operandToInstructionStep(node1.asOperand(), node2.asInstruction())
62-
}
63-
64-
predicate instructionToOperandStep(Instruction instr, Operand operand) { operand.getDef() = instr }
65-
66-
/**
67-
* Holds if `operand` flows to the result of `instr`.
68-
*
69-
* This predicate ignores flow through `PhiInstruction`s to create a 'must flow' relation. It also
70-
* intentionally conflates addresses of fields and their object, and pointer offsets with their
71-
* base pointer as this allows us to detect cases where an object's address flows to a return statement
72-
* via a field. For example:
73-
*
74-
* ```cpp
75-
* struct S { int x, y };
76-
* int* test() {
77-
* S s;
78-
* return &s.x; // BAD: &s.x is an address of a variable on the stack.
79-
* }
80-
* ```
81-
*/
82-
predicate operandToInstructionStep(Operand operand, Instruction instr) {
83-
instr.(CopyInstruction).getSourceValueOperand() = operand
84-
or
85-
instr.(ConvertInstruction).getUnaryOperand() = operand
86-
or
87-
instr.(CheckedConvertOrNullInstruction).getUnaryOperand() = operand
88-
or
89-
instr.(InheritanceConversionInstruction).getUnaryOperand() = operand
90-
or
91-
instr.(FieldAddressInstruction).getObjectAddressOperand() = operand
92-
or
93-
instr.(PointerOffsetInstruction).getLeftOperand() = operand
94-
}
95-
96-
/** Holds if a source node flows to `n`. */
97-
predicate branchlessLocalFlow0(Node n) {
98-
isSource(n)
99-
or
100-
exists(Node mid |
101-
branchlessLocalFlow0(mid) and
102-
step(mid, n)
103-
)
104-
}
105-
106-
/** Holds if `n` is reachable through some source node, and `n` also eventually reaches a sink. */
107-
predicate branchlessLocalFlow1(Node n) {
108-
branchlessLocalFlow0(n) and
109-
(
110-
isSink(n)
111-
or
112-
exists(Node mid |
113-
branchlessLocalFlow1(mid) and
114-
step(n, mid)
29+
class ReturnStackAllocatedMemoryConfig extends MustFlowConfiguration {
30+
ReturnStackAllocatedMemoryConfig() { this = "ReturnStackAllocatedMemoryConfig" }
31+
32+
override predicate isSource(DataFlow::Node source) {
33+
// Holds if `source` is a node that represents the use of a stack variable
34+
exists(VariableAddressInstruction var, Function func |
35+
var = source.asInstruction() and
36+
func = var.getEnclosingFunction() and
37+
var.getASTVariable() instanceof StackVariable and
38+
// Pointer-to-member types aren't properly handled in the dbscheme.
39+
not var.getResultType() instanceof PointerToMemberType and
40+
// Rule out FPs caused by extraction errors.
41+
not any(ErrorExpr e).getEnclosingFunction() = func and
42+
not intentionallyReturnsStackPointer(func)
11543
)
116-
)
117-
}
44+
}
11845

119-
newtype TLocalPathNode =
120-
TLocalPathNodeMid(Node n) {
121-
branchlessLocalFlow1(n) and
122-
(
123-
isSource(n) or
124-
exists(LocalPathNodeMid mid | step(mid.getNode(), n))
46+
override predicate isSink(DataFlow::Node sink) {
47+
// Holds if `sink` is a node that represents the `StoreInstruction` that is subsequently used in
48+
// a `ReturnValueInstruction`.
49+
// We use the `StoreInstruction` instead of the instruction that defines the
50+
// `ReturnValueInstruction`'s source value oprand because the former has better location information.
51+
exists(StoreInstruction store |
52+
store.getDestinationAddress().(VariableAddressInstruction).getIRVariable() instanceof
53+
IRReturnVariable and
54+
sink.asOperand() = store.getSourceValueOperand()
12555
)
12656
}
12757

128-
abstract class LocalPathNode extends TLocalPathNode {
129-
Node n;
130-
131-
/** Gets the underlying node. */
132-
Node getNode() { result = n }
133-
134-
/** Gets a textual representation of this node. */
135-
string toString() { result = n.toString() }
136-
137-
/** Gets the location of this element. */
138-
Location getLocation() { result = n.getLocation() }
139-
140-
/** Gets a successor `LocalPathNode`, if any. */
141-
LocalPathNode getASuccessor() { step(this.getNode(), result.getNode()) }
142-
}
143-
144-
class LocalPathNodeMid extends LocalPathNode, TLocalPathNodeMid {
145-
LocalPathNodeMid() { this = TLocalPathNodeMid(n) }
146-
}
147-
148-
class LocalPathNodeSink extends LocalPathNodeMid {
149-
LocalPathNodeSink() { isSink(this.getNode()) }
150-
}
151-
152-
/**
153-
* Holds if `source` is a source node, `sink` is a sink node, and there's flow
154-
* from `source` to `sink` using `step` relation.
155-
*/
156-
predicate hasFlow(LocalPathNode source, LocalPathNodeSink sink) {
157-
isSource(source.getNode()) and
158-
source.getASuccessor+() = sink
159-
}
160-
161-
predicate reach(LocalPathNode n) { n instanceof LocalPathNodeSink or reach(n.getASuccessor()) }
162-
163-
query predicate edges(LocalPathNode a, LocalPathNode b) { a.getASuccessor() = b and reach(b) }
164-
165-
query predicate nodes(LocalPathNode n, string key, string val) {
166-
reach(n) and key = "semmle.label" and val = n.toString()
58+
/**
59+
* This configuration intentionally conflates addresses of fields and their object, and pointer offsets
60+
* with their base pointer as this allows us to detect cases where an object's address flows to a
61+
* return statement via a field. For example:
62+
*
63+
* ```cpp
64+
* struct S { int x, y };
65+
* int* test() {
66+
* S s;
67+
* return &s.x; // BAD: &s.x is an address of a variable on the stack.
68+
* }
69+
*/
70+
override predicate isAdditionalFlowStep(DataFlow::Node node1, DataFlow::Node node2) {
71+
node2.asInstruction().(FieldAddressInstruction).getObjectAddressOperand() = node1.asOperand()
72+
or
73+
node2.asInstruction().(PointerOffsetInstruction).getLeftOperand() = node1.asOperand()
74+
}
16775
}
16876

169-
from LocalPathNode source, LocalPathNodeSink sink, VariableAddressInstruction var
77+
from
78+
MustFlowPathNode source, MustFlowPathNode sink, VariableAddressInstruction var,
79+
ReturnStackAllocatedMemoryConfig conf
17080
where
171-
hasFlow(source, sink) and
172-
source.getNode().asInstruction() = var
81+
conf.hasFlowPath(source, sink) and
82+
source.getNode().asInstruction() = var and
83+
// Only raise an alert if we're returning from the _same_ callable as the on that
84+
// declared the stack variable.
85+
var.getEnclosingFunction() = sink.getNode().getEnclosingCallable()
17386
select sink.getNode(), source, sink, "May return stack-allocated memory from $@.", var.getAST(),
17487
var.getAST().toString()

0 commit comments

Comments
 (0)