|
3 | 3 | * @description A function returns a pointer to a stack-allocated region of
|
4 | 4 | * memory. This memory is deallocated at the end of the function,
|
5 | 5 | * which may lead the caller to dereference a dangling pointer.
|
6 |
| - * @kind problem |
| 6 | + * @kind path-problem |
7 | 7 | * @id cpp/return-stack-allocated-memory
|
8 | 8 | * @problem.severity warning
|
9 | 9 | * @precision high
|
|
12 | 12 | */
|
13 | 13 |
|
14 | 14 | 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 |
18 | 17 |
|
19 | 18 | /**
|
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 |
23 | 20 | */
|
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 | + ) |
28 | 30 | }
|
29 | 31 |
|
30 | 32 | /**
|
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. |
34 | 36 | */
|
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() |
41 | 42 | )
|
| 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 |
42 | 74 | 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 |
46 | 76 | or
|
47 |
| - hasNontrivialConversion(e.getConversion()) |
| 77 | + instr.(InheritanceConversionInstruction).getUnaryOperand() = operand |
| 78 | + or |
| 79 | + instr.(FieldAddressInstruction).getObjectAddressOperand() = operand |
| 80 | + or |
| 81 | + instr.(PointerOffsetInstruction).getLeftOperand() = operand |
48 | 82 | }
|
49 | 83 |
|
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 |
55 | 97 | (
|
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) |
60 | 99 | 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) |
68 | 103 | )
|
69 | 104 | )
|
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