Skip to content

Commit 725004a

Browse files
committed
C++: Modernize use-after-free query using dataflow.
1 parent 17fe5f2 commit 725004a

File tree

1 file changed

+117
-41
lines changed

1 file changed

+117
-41
lines changed

cpp/ql/src/Critical/UseAfterFree.ql

Lines changed: 117 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
/**
22
* @name Potential use after free
33
* @description An allocated memory block is used after it has been freed. Behavior in such cases is undefined and can cause memory corruption.
4-
* @kind problem
4+
* @kind path-problem
5+
* @precision medium
56
* @id cpp/use-after-free
67
* @problem.severity warning
78
* @security-severity 9.3
@@ -11,56 +12,131 @@
1112
*/
1213

1314
import cpp
14-
import semmle.code.cpp.controlflow.StackVariableReachability
15-
16-
/** `e` is an expression that frees the memory pointed to by `v`. */
17-
predicate isFreeExpr(Expr e, StackVariable v) {
18-
exists(VariableAccess va | va.getTarget() = v |
19-
exists(FunctionCall fc | fc = e |
20-
fc.getTarget().hasGlobalOrStdName("free") and
21-
va = fc.getArgument(0)
22-
)
15+
import semmle.code.cpp.dataflow.new.DataFlow
16+
import semmle.code.cpp.ir.IR
17+
import FlowAfterFree
18+
import UseAfterFree::PathGraph
19+
20+
/**
21+
* Holds if `call` is a call to a function that obviously
22+
* doesn't dereference its `i`'th argument.
23+
*/
24+
private predicate externalCallNeverDereferences(FormattingFunctionCall call, int arg) {
25+
exists(int formatArg |
26+
pragma[only_bind_out](call.getFormatArgument(formatArg)) =
27+
pragma[only_bind_out](call.getArgument(arg)) and
28+
call.getFormat().(FormatLiteral).getConvSpec(formatArg) != "%s"
29+
)
30+
}
31+
32+
predicate isUse0(DataFlow::Node n, Expr e) {
33+
e = n.asExpr() and
34+
not isFree(_, e, _) and
35+
(
36+
e = any(PointerDereferenceExpr pde).getOperand()
2337
or
24-
e.(DeleteExpr).getExpr() = va
38+
e = any(PointerFieldAccess pfa).getQualifier()
2539
or
26-
e.(DeleteArrayExpr).getExpr() = va
40+
e = any(ArrayExpr ae).getArrayBase()
41+
or
42+
// Assume any function without a body will dereference the pointer
43+
exists(int i, Call call, Function f |
44+
n.asExpr() = call.getArgument(i) and
45+
f = call.getTarget() and
46+
not f.hasEntryPoint() and
47+
// Exclude known functions we know won't dereference the pointer.
48+
// For example, a call such as `printf("%p", myPointer)`.
49+
not externalCallNeverDereferences(call, i)
50+
)
2751
)
2852
}
2953

30-
/** `e` is an expression that (may) dereference `v`. */
31-
predicate isDerefExpr(Expr e, StackVariable v) {
32-
v.getAnAccess() = e and dereferenced(e)
33-
or
34-
isDerefByCallExpr(_, _, e, v)
35-
}
54+
module ParameterSinks {
55+
import semmle.code.cpp.ir.ValueNumbering
3656

37-
/**
38-
* `va` is passed by value as (part of) the `i`th argument in
39-
* call `c`. The target function is either a library function
40-
* or a source code function that dereferences the relevant
41-
* parameter.
42-
*/
43-
predicate isDerefByCallExpr(Call c, int i, VariableAccess va, StackVariable v) {
44-
v.getAnAccess() = va and
45-
va = c.getAnArgumentSubExpr(i) and
46-
not c.passesByReference(i, va) and
47-
(c.getTarget().hasEntryPoint() implies isDerefExpr(_, c.getTarget().getParameter(i)))
48-
}
57+
predicate flowsToUse(DataFlow::Node n) {
58+
isUse0(n, _)
59+
or
60+
exists(DataFlow::Node succ |
61+
flowsToUse(succ) and
62+
DataFlow::localFlowStep(n, succ)
63+
)
64+
}
4965

50-
class UseAfterFreeReachability extends StackVariableReachability {
51-
UseAfterFreeReachability() { this = "UseAfterFree" }
66+
private predicate flowsFromParam(DataFlow::Node n) {
67+
flowsToUse(n) and
68+
(
69+
n.asParameter().getUnspecifiedType() instanceof PointerType
70+
or
71+
exists(DataFlow::Node prev |
72+
flowsFromParam(prev) and
73+
DataFlow::localFlowStep(prev, n)
74+
)
75+
)
76+
}
5277

53-
override predicate isSource(ControlFlowNode node, StackVariable v) { isFreeExpr(node, v) }
78+
private predicate step(DataFlow::Node n1, DataFlow::Node n2) {
79+
flowsFromParam(n1) and
80+
flowsFromParam(n2) and
81+
DataFlow::localFlowStep(n1, n2)
82+
}
5483

55-
override predicate isSink(ControlFlowNode node, StackVariable v) { isDerefExpr(node, v) }
84+
private predicate paramToUse(DataFlow::Node n1, DataFlow::Node n2) = fastTC(step/2)(n1, n2)
5685

57-
override predicate isBarrier(ControlFlowNode node, StackVariable v) {
58-
definitionBarrier(v, node) or
59-
isFreeExpr(node, v)
86+
private predicate hasFlow(
87+
DataFlow::Node source, InitializeParameterInstruction init, DataFlow::Node sink
88+
) {
89+
pragma[only_bind_out](source.asParameter()) = pragma[only_bind_out](init.getParameter()) and
90+
paramToUse(source, sink) and
91+
isUse0(sink, _)
92+
}
93+
94+
private InitializeParameterInstruction getAnAlwaysDereferencedParameter0() {
95+
exists(DataFlow::Node source, DataFlow::Node sink, IRBlock b1, int i1, IRBlock b2, int i2 |
96+
hasFlow(pragma[only_bind_into](source), result, pragma[only_bind_into](sink)) and
97+
source.hasIndexInBlock(b1, i1) and
98+
sink.hasIndexInBlock(b2, i2) and
99+
strictlyPostDominates(b2, i2, b1, i1)
100+
)
60101
}
102+
103+
private CallInstruction getAnAlwaysReachedCallInstruction(IRFunction f) {
104+
result.getBlock().postDominates(f.getEntryBlock())
105+
}
106+
107+
InitializeParameterInstruction getAnAlwaysDereferencedParameter() {
108+
result = getAnAlwaysDereferencedParameter0()
109+
or
110+
exists(CallInstruction call, int i, InitializeParameterInstruction p |
111+
pragma[only_bind_out](call.getStaticCallTarget()) =
112+
pragma[only_bind_out](p.getEnclosingFunction()) and
113+
p.hasIndex(i) and
114+
p = getAnAlwaysDereferencedParameter() and
115+
result = valueNumber(call.getArgument(i)).getAnInstruction() and
116+
call = getAnAlwaysReachedCallInstruction(_)
117+
)
118+
}
119+
}
120+
121+
predicate isUse(DataFlow::Node n, Expr e) {
122+
isUse0(n, e)
123+
or
124+
exists(CallInstruction call, int i, InitializeParameterInstruction init |
125+
n.asOperand().getDef().getUnconvertedResultExpression() = e and
126+
init = ParameterSinks::getAnAlwaysDereferencedParameter() and
127+
call.getArgumentOperand(i) = n.asOperand() and
128+
init.hasIndex(i) and
129+
init.getEnclosingFunction() = call.getStaticCallTarget()
130+
)
61131
}
62132

63-
from UseAfterFreeReachability r, StackVariable v, Expr free, Expr e
64-
where r.reaches(free, v, e)
65-
select e, "Memory pointed to by '" + v.getName().toString() + "' may have $@.", free,
66-
"been previously freed"
133+
predicate excludeNothing(DeallocationExpr dealloc, Expr e) { none() }
134+
135+
module UseAfterFree = FlowFromFree<isUse/2, excludeNothing/2>;
136+
137+
from UseAfterFree::PathNode source, UseAfterFree::PathNode sink, DeallocationExpr dealloc
138+
where
139+
UseAfterFree::flowPath(source, sink) and
140+
isFree(source.getNode(), _, dealloc)
141+
select sink.getNode(), source, sink, "Memory may have been previously freed by $@.", dealloc,
142+
dealloc.toString()

0 commit comments

Comments
 (0)