Skip to content

Commit e689f6b

Browse files
committed
C++: Use the IR for 'cpp/return-stack-allocated-memory'.
1 parent 78642aa commit e689f6b

File tree

4 files changed

+371
-56
lines changed

4 files changed

+371
-56
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ Record* fixRecord(Record* r) {
44

55
myRecord.fix();
66
return &myRecord; //returns reference to myRecord, which is a stack-allocated object
7-
}
7+
}

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

Lines changed: 133 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* @description A function returns a pointer to a stack-allocated region of
44
* memory. This memory is deallocated at the end of the function,
55
* which may lead the caller to dereference a dangling pointer.
6-
* @kind problem
6+
* @kind path-problem
77
* @id cpp/return-stack-allocated-memory
88
* @problem.severity warning
99
* @precision high
@@ -12,59 +12,151 @@
1212
*/
1313

1414
import cpp
15-
import semmle.code.cpp.dataflow.EscapesTree
16-
import semmle.code.cpp.models.interfaces.PointerWrapper
17-
import semmle.code.cpp.dataflow.DataFlow
15+
import semmle.code.cpp.ir.IR
16+
import semmle.code.cpp.ir.dataflow.DataFlow::DataFlow
1817

1918
/**
20-
* Holds if `n1` may flow to `n2`, ignoring flow through fields because these
21-
* are currently modeled as an overapproximation that assumes all objects may
22-
* alias.
19+
* Holds if `source` is a node that represents the use of a stack variable
2320
*/
24-
predicate conservativeDataFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
25-
DataFlow::localFlowStep(n1, n2) and
26-
not n2.asExpr() instanceof FieldAccess and
27-
not hasNontrivialConversion(n2.asExpr())
21+
predicate isSource(Node source) {
22+
exists(VariableAddressInstruction var |
23+
var = source.asInstruction() and
24+
var.getASTVariable() instanceof StackVariable and
25+
// Pointer-to-member types aren't properly handled in the dbscheme.
26+
not var.getResultType() instanceof PointerToMemberType and
27+
// Rule out FPs caused by extraction errors.
28+
not any(ErrorExpr e).getEnclosingFunction() = var.getEnclosingFunction()
29+
)
2830
}
2931

3032
/**
31-
* Holds if `e` has a conversion that changes it from lvalue to pointer or
32-
* back. As the data-flow library does not support conversions, we cannot track
33-
* data flow through such expressions.
33+
* Holds if `sink` is a node that represents the `StoreInstruction` that is subsequently used in
34+
* a `ReturnValueInstruction`. We use the `StoreInstruction` instead of the instruction that defines the
35+
* `ReturnValueInstruction`'s source value oprand because the former has better location information.
3436
*/
35-
predicate hasNontrivialConversion(Expr e) {
36-
e instanceof Conversion and
37-
not (
38-
e instanceof Cast
39-
or
40-
e instanceof ParenthesisExpr
37+
predicate isSink(Node sink) {
38+
exists(StoreInstruction store |
39+
store.getDestinationAddress().(VariableAddressInstruction).getIRVariable() instanceof
40+
IRReturnVariable and
41+
sink.asOperand() = store.getSourceValueOperand()
4142
)
43+
}
44+
45+
/** Holds if `node1` _must_ flow to `node2`. */
46+
predicate step(Node node1, Node node2) {
47+
instructionToOperandStep(node1.asInstruction(), node2.asOperand())
48+
or
49+
operandToInstructionStep(node1.asOperand(), node2.asInstruction())
50+
}
51+
52+
predicate instructionToOperandStep(Instruction instr, Operand operand) { operand.getDef() = instr }
53+
54+
/**
55+
* Holds if `operand` flows to the result of `instr`.
56+
*
57+
* This predicate ignores flow through `PhiInstruction`s to create a 'must flow' relation. It also
58+
* intentionally conflates addresses of fields and their object, and pointer offsets with their
59+
* base pointer as this allows us to detect cases where an object's address flows to a return statement
60+
* via a field. For example:
61+
*
62+
* ```cpp
63+
* struct S { int x, y };
64+
* int* test() {
65+
* S s;
66+
* return &s.x; // BAD: &s.x is an address of a variable on the stack.
67+
* }
68+
* ```
69+
*/
70+
predicate operandToInstructionStep(Operand operand, Instruction instr) {
71+
instr.(CopyInstruction).getSourceValueOperand() = operand
72+
or
73+
instr.(ConvertInstruction).getUnaryOperand() = operand
4274
or
43-
// A smart pointer can be stack-allocated while the data it points to is heap-allocated.
44-
// So we exclude such "conversions" from this predicate.
45-
e = any(PointerWrapper wrapper).getAnUnwrapperFunction().getACallToThisFunction()
75+
instr.(CheckedConvertOrNullInstruction).getUnaryOperand() = operand
4676
or
47-
hasNontrivialConversion(e.getConversion())
77+
instr.(InheritanceConversionInstruction).getUnaryOperand() = operand
78+
or
79+
instr.(FieldAddressInstruction).getObjectAddressOperand() = operand
80+
or
81+
instr.(PointerOffsetInstruction).getLeftOperand() = operand
4882
}
4983

50-
from StackVariable var, VariableAccess va, ReturnStmt r
51-
where
52-
not var.getUnspecifiedType() instanceof ReferenceType and
53-
not r.isFromUninstantiatedTemplate(_) and
54-
va = var.getAnAccess() and
84+
/** Holds if a source node flows to `n`. */
85+
predicate branchlessLocalFlow0(Node n) {
86+
isSource(n)
87+
or
88+
exists(Node mid |
89+
branchlessLocalFlow0(mid) and
90+
step(mid, n)
91+
)
92+
}
93+
94+
/** Holds if `n` is reachable through some source node, and `n` also eventually reaches a sink. */
95+
predicate branchlessLocalFlow1(Node n) {
96+
branchlessLocalFlow0(n) and
5597
(
56-
// To check if the address escapes directly from `e` in `return e`, we need
57-
// to check the fully-converted `e` in case there are implicit
58-
// array-to-pointer conversions or reference conversions.
59-
variableAddressEscapesTree(va, r.getExpr().getFullyConverted())
98+
isSink(n)
6099
or
61-
// The data flow library doesn't support conversions, so here we check that
62-
// the address escapes into some expression `pointerToLocal`, which flows
63-
// in one or more steps to a returned expression.
64-
exists(Expr pointerToLocal |
65-
variableAddressEscapesTree(va, pointerToLocal.getFullyConverted()) and
66-
not hasNontrivialConversion(pointerToLocal) and
67-
conservativeDataFlowStep+(DataFlow::exprNode(pointerToLocal), DataFlow::exprNode(r.getExpr()))
100+
exists(Node mid |
101+
branchlessLocalFlow1(mid) and
102+
step(n, mid)
68103
)
69104
)
70-
select r, "May return stack-allocated memory from $@.", va, va.toString()
105+
}
106+
107+
newtype TLocalPathNode =
108+
TLocalPathNodeMid(Node n) {
109+
branchlessLocalFlow1(n) and
110+
(
111+
isSource(n) or
112+
exists(LocalPathNodeMid mid | step(mid.getNode(), n))
113+
)
114+
}
115+
116+
abstract class LocalPathNode extends TLocalPathNode {
117+
Node n;
118+
119+
/** Gets the underlying node. */
120+
Node getNode() { result = n }
121+
122+
/** Gets a textual representation of this node. */
123+
string toString() { result = n.toString() }
124+
125+
/** Gets the location of this element. */
126+
Location getLocation() { result = n.getLocation() }
127+
128+
/** Gets a successor `LocalPathNode`, if any. */
129+
LocalPathNode getASuccessor() { step(this.getNode(), result.getNode()) }
130+
}
131+
132+
class LocalPathNodeMid extends LocalPathNode, TLocalPathNodeMid {
133+
LocalPathNodeMid() { this = TLocalPathNodeMid(n) }
134+
}
135+
136+
class LocalPathNodeSink extends LocalPathNodeMid {
137+
LocalPathNodeSink() { isSink(this.getNode()) }
138+
}
139+
140+
/**
141+
* Holds if `source` is a source node, `sink` is a sink node, and there's flow
142+
* from `source` to `sink` using `step` relation.
143+
*/
144+
predicate hasFlow(LocalPathNode source, LocalPathNodeSink sink) {
145+
isSource(source.getNode()) and
146+
source.getASuccessor+() = sink
147+
}
148+
149+
predicate reach(LocalPathNode n) { n instanceof LocalPathNodeSink or reach(n.getASuccessor()) }
150+
151+
query predicate edges(LocalPathNode a, LocalPathNode b) { a.getASuccessor() = b and reach(b) }
152+
153+
query predicate nodes(LocalPathNode n, string key, string val) {
154+
reach(n) and key = "semmle.label" and val = n.toString()
155+
}
156+
157+
from LocalPathNode source, LocalPathNodeSink sink, VariableAddressInstruction var
158+
where
159+
hasFlow(source, sink) and
160+
source.getNode().asInstruction() = var
161+
select sink.getNode(), source, sink, "May return stack-allocated memory from $@.", var.getAST(),
162+
var.getAST().toString()

0 commit comments

Comments
 (0)