Skip to content

Commit 967526b

Browse files
committed
Separating out use after free logic into a library and a ql so the query can be expanded easily.
1 parent 8bd682b commit 967526b

File tree

2 files changed

+174
-168
lines changed

2 files changed

+174
-168
lines changed

cpp/ql/src/Critical/UseAfterFree.ql

Lines changed: 5 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -15,177 +15,14 @@ import cpp
1515
import semmle.code.cpp.dataflow.new.DataFlow
1616
import semmle.code.cpp.ir.IR
1717
import FlowAfterFree
18-
import UseAfterFree::PathGraph
18+
import UseAfterFree
19+
import UseAfterFreeTrace::PathGraph
1920

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(Expr e) {
33-
not isFree(_, _, e, _) and
34-
(
35-
e = any(PointerDereferenceExpr pde).getOperand()
36-
or
37-
e = any(PointerFieldAccess pfa).getQualifier()
38-
or
39-
e = any(ArrayExpr ae).getArrayBase()
40-
or
41-
e = any(Call call).getQualifier()
42-
or
43-
// Assume any function without a body will dereference the pointer
44-
exists(int i, Call call, Function f |
45-
e = call.getArgument(i) and
46-
f = call.getTarget() and
47-
not f.hasEntryPoint() and
48-
// Exclude known functions we know won't dereference the pointer.
49-
// For example, a call such as `printf("%p", myPointer)`.
50-
not externalCallNeverDereferences(call, i)
51-
)
52-
)
53-
}
54-
55-
module ParameterSinks {
56-
import semmle.code.cpp.ir.ValueNumbering
57-
58-
predicate flowsToUse(DataFlow::Node n) {
59-
isUse0(n.asExpr())
60-
or
61-
exists(DataFlow::Node succ |
62-
flowsToUse(succ) and
63-
DataFlow::localFlowStep(n, succ)
64-
)
65-
}
66-
67-
private predicate flowsFromParam(DataFlow::Node n) {
68-
flowsToUse(n) and
69-
(
70-
n.asParameter().getUnspecifiedType() instanceof PointerType
71-
or
72-
exists(DataFlow::Node prev |
73-
flowsFromParam(prev) and
74-
DataFlow::localFlowStep(prev, n)
75-
)
76-
)
77-
}
78-
79-
private predicate step(DataFlow::Node n1, DataFlow::Node n2) {
80-
flowsFromParam(n1) and
81-
flowsFromParam(n2) and
82-
DataFlow::localFlowStep(n1, n2)
83-
}
84-
85-
private predicate paramToUse(DataFlow::Node n1, DataFlow::Node n2) = fastTC(step/2)(n1, n2)
86-
87-
private predicate hasFlow(
88-
DataFlow::Node source, InitializeParameterInstruction init, DataFlow::Node sink
89-
) {
90-
pragma[only_bind_out](source.asParameter()) = pragma[only_bind_out](init.getParameter()) and
91-
paramToUse(source, sink) and
92-
isUse0(sink.asExpr())
93-
}
94-
95-
private InitializeParameterInstruction getAnAlwaysDereferencedParameter0() {
96-
exists(DataFlow::Node source, DataFlow::Node sink, IRBlock b1, int i1, IRBlock b2, int i2 |
97-
hasFlow(pragma[only_bind_into](source), result, pragma[only_bind_into](sink)) and
98-
source.hasIndexInBlock(b1, pragma[only_bind_into](i1)) and
99-
sink.hasIndexInBlock(b2, pragma[only_bind_into](i2)) and
100-
strictlyPostDominates(b2, i2, b1, i1)
101-
)
102-
}
103-
104-
private CallInstruction getAnAlwaysReachedCallInstruction() {
105-
exists(IRFunction f | result.getBlock().postDominates(f.getEntryBlock()))
106-
}
107-
108-
pragma[nomagic]
109-
private predicate callHasTargetAndArgument(Function f, int i, Instruction argument) {
110-
exists(CallInstruction call |
111-
call.getStaticCallTarget() = f and
112-
call.getArgument(i) = argument and
113-
call = getAnAlwaysReachedCallInstruction()
114-
)
115-
}
116-
117-
pragma[nomagic]
118-
private predicate initializeParameterInFunction(Function f, int i) {
119-
exists(InitializeParameterInstruction init |
120-
pragma[only_bind_out](init.getEnclosingFunction()) = f and
121-
init.hasIndex(i) and
122-
init = getAnAlwaysDereferencedParameter()
123-
)
124-
}
125-
126-
pragma[nomagic]
127-
private predicate alwaysDereferencedArgumentHasValueNumber(ValueNumber vn) {
128-
exists(int i, Function f, Instruction argument |
129-
callHasTargetAndArgument(f, i, argument) and
130-
initializeParameterInFunction(pragma[only_bind_into](f), pragma[only_bind_into](i)) and
131-
vn.getAnInstruction() = argument
132-
)
133-
}
134-
135-
InitializeParameterInstruction getAnAlwaysDereferencedParameter() {
136-
result = getAnAlwaysDereferencedParameter0()
137-
or
138-
exists(ValueNumber vn |
139-
alwaysDereferencedArgumentHasValueNumber(vn) and
140-
vn.getAnInstruction() = result
141-
)
142-
}
143-
}
144-
145-
module IsUse {
146-
private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplCommon
147-
148-
predicate isUse(DataFlow::Node n, Expr e) {
149-
isUse0(e) and n.asExpr() = e
150-
or
151-
exists(CallInstruction call, InitializeParameterInstruction init |
152-
n.asOperand().getDef().getUnconvertedResultExpression() = e and
153-
pragma[only_bind_into](init) = ParameterSinks::getAnAlwaysDereferencedParameter() and
154-
viableParamArg(call, DataFlow::instructionNode(init), n) and
155-
pragma[only_bind_out](init.getEnclosingFunction()) =
156-
pragma[only_bind_out](call.getStaticCallTarget())
157-
)
158-
}
159-
}
160-
161-
import IsUse
162-
163-
/**
164-
* `dealloc1` is a deallocation expression, `e` is an expression that dereferences a
165-
* pointer, and the `(dealloc1, e)` pair should be excluded by the `FlowFromFree` library.
166-
*/
167-
bindingset[dealloc1, e]
168-
predicate isExcludeFreeUsePair(DeallocationExpr dealloc1, Expr e) {
169-
// From https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmfreepagesfrommdl:
170-
// "After calling MmFreePagesFromMdl, the caller must also call ExFreePool
171-
// to release the memory that was allocated for the MDL structure."
172-
dealloc1.(FunctionCall).getTarget().hasGlobalName("MmFreePagesFromMdl") and
173-
isExFreePoolCall(_, e)
174-
}
175-
176-
module UseAfterFreeParam implements FlowFromFreeParamSig {
177-
predicate isSink = isUse/2;
178-
179-
predicate isExcluded = isExcludeFreeUsePair/2;
180-
181-
predicate sourceSinkIsRelated = defaultSourceSinkIsRelated/2;
182-
}
183-
184-
module UseAfterFree = FlowFromFree<UseAfterFreeParam>;
21+
module UseAfterFreeTrace = FlowFromFree<UseAfterFreeParam>;
18522

186-
from UseAfterFree::PathNode source, UseAfterFree::PathNode sink, DeallocationExpr dealloc
23+
from UseAfterFreeTrace::PathNode source, UseAfterFreeTrace::PathNode sink, DeallocationExpr dealloc
18724
where
188-
UseAfterFree::flowPath(source, sink) and
25+
UseAfterFreeTrace::flowPath(source, sink) and
18926
isFree(source.getNode(), _, _, dealloc)
19027
select sink.getNode(), source, sink, "Memory may have been previously freed by $@.", dealloc,
19128
dealloc.toString()

cpp/ql/src/Critical/UseAfterFree.qll

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import cpp
2+
private import FlowAfterFree
3+
private import semmle.code.cpp.ir.IR
4+
5+
/**
6+
* Holds if `call` is a call to a function that obviously
7+
* doesn't dereference its `i`'th argument.
8+
*/
9+
private predicate externalCallNeverDereferences(FormattingFunctionCall call, int arg) {
10+
exists(int formatArg |
11+
pragma[only_bind_out](call.getFormatArgument(formatArg)) =
12+
pragma[only_bind_out](call.getArgument(arg)) and
13+
call.getFormat().(FormatLiteral).getConvSpec(formatArg) != "%s"
14+
)
15+
}
16+
17+
predicate isUse0(Expr e) {
18+
not isFree(_, _, e, _) and
19+
(
20+
e = any(PointerDereferenceExpr pde).getOperand()
21+
or
22+
e = any(PointerFieldAccess pfa).getQualifier()
23+
or
24+
e = any(ArrayExpr ae).getArrayBase()
25+
or
26+
e = any(Call call).getQualifier()
27+
or
28+
// Assume any function without a body will dereference the pointer
29+
exists(int i, Call call, Function f |
30+
e = call.getArgument(i) and
31+
f = call.getTarget() and
32+
not f.hasEntryPoint() and
33+
// Exclude known functions we know won't dereference the pointer.
34+
// For example, a call such as `printf("%p", myPointer)`.
35+
not externalCallNeverDereferences(call, i)
36+
)
37+
)
38+
}
39+
40+
module ParameterSinks {
41+
import semmle.code.cpp.ir.ValueNumbering
42+
43+
predicate flowsToUse(DataFlow::Node n) {
44+
isUse0(n.asExpr())
45+
or
46+
exists(DataFlow::Node succ |
47+
flowsToUse(succ) and
48+
DataFlow::localFlowStep(n, succ)
49+
)
50+
}
51+
52+
private predicate flowsFromParam(DataFlow::Node n) {
53+
flowsToUse(n) and
54+
(
55+
n.asParameter().getUnspecifiedType() instanceof PointerType
56+
or
57+
exists(DataFlow::Node prev |
58+
flowsFromParam(prev) and
59+
DataFlow::localFlowStep(prev, n)
60+
)
61+
)
62+
}
63+
64+
private predicate step(DataFlow::Node n1, DataFlow::Node n2) {
65+
flowsFromParam(n1) and
66+
flowsFromParam(n2) and
67+
DataFlow::localFlowStep(n1, n2)
68+
}
69+
70+
private predicate paramToUse(DataFlow::Node n1, DataFlow::Node n2) = fastTC(step/2)(n1, n2)
71+
72+
private predicate hasFlow(
73+
DataFlow::Node source, InitializeParameterInstruction init, DataFlow::Node sink
74+
) {
75+
pragma[only_bind_out](source.asParameter()) = pragma[only_bind_out](init.getParameter()) and
76+
paramToUse(source, sink) and
77+
isUse0(sink.asExpr())
78+
}
79+
80+
private InitializeParameterInstruction getAnAlwaysDereferencedParameter0() {
81+
exists(DataFlow::Node source, DataFlow::Node sink, IRBlock b1, int i1, IRBlock b2, int i2 |
82+
hasFlow(pragma[only_bind_into](source), result, pragma[only_bind_into](sink)) and
83+
source.hasIndexInBlock(b1, pragma[only_bind_into](i1)) and
84+
sink.hasIndexInBlock(b2, pragma[only_bind_into](i2)) and
85+
strictlyPostDominates(b2, i2, b1, i1)
86+
)
87+
}
88+
89+
private CallInstruction getAnAlwaysReachedCallInstruction() {
90+
exists(IRFunction f | result.getBlock().postDominates(f.getEntryBlock()))
91+
}
92+
93+
pragma[nomagic]
94+
private predicate callHasTargetAndArgument(Function f, int i, Instruction argument) {
95+
exists(CallInstruction call |
96+
call.getStaticCallTarget() = f and
97+
call.getArgument(i) = argument and
98+
call = getAnAlwaysReachedCallInstruction()
99+
)
100+
}
101+
102+
pragma[nomagic]
103+
private predicate initializeParameterInFunction(Function f, int i) {
104+
exists(InitializeParameterInstruction init |
105+
pragma[only_bind_out](init.getEnclosingFunction()) = f and
106+
init.hasIndex(i) and
107+
init = getAnAlwaysDereferencedParameter()
108+
)
109+
}
110+
111+
pragma[nomagic]
112+
private predicate alwaysDereferencedArgumentHasValueNumber(ValueNumber vn) {
113+
exists(int i, Function f, Instruction argument |
114+
callHasTargetAndArgument(f, i, argument) and
115+
initializeParameterInFunction(pragma[only_bind_into](f), pragma[only_bind_into](i)) and
116+
vn.getAnInstruction() = argument
117+
)
118+
}
119+
120+
InitializeParameterInstruction getAnAlwaysDereferencedParameter() {
121+
result = getAnAlwaysDereferencedParameter0()
122+
or
123+
exists(ValueNumber vn |
124+
alwaysDereferencedArgumentHasValueNumber(vn) and
125+
vn.getAnInstruction() = result
126+
)
127+
}
128+
}
129+
130+
module IsUse {
131+
private import semmle.code.cpp.ir.dataflow.internal.DataFlowImplCommon
132+
133+
predicate isUse(DataFlow::Node n, Expr e) {
134+
isUse0(e) and n.asExpr() = e
135+
or
136+
exists(CallInstruction call, InitializeParameterInstruction init |
137+
n.asOperand().getDef().getUnconvertedResultExpression() = e and
138+
pragma[only_bind_into](init) = ParameterSinks::getAnAlwaysDereferencedParameter() and
139+
viableParamArg(call, DataFlow::instructionNode(init), n) and
140+
pragma[only_bind_out](init.getEnclosingFunction()) =
141+
pragma[only_bind_out](call.getStaticCallTarget())
142+
)
143+
}
144+
}
145+
146+
import IsUse
147+
148+
/**
149+
* `dealloc1` is a deallocation expression, `e` is an expression that dereferences a
150+
* pointer, and the `(dealloc1, e)` pair should be excluded by the `FlowFromFree` library.
151+
*/
152+
bindingset[dealloc1, e]
153+
predicate isExcludeFreeUsePair(DeallocationExpr dealloc1, Expr e) {
154+
// From https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmfreepagesfrommdl:
155+
// "After calling MmFreePagesFromMdl, the caller must also call ExFreePool
156+
// to release the memory that was allocated for the MDL structure."
157+
dealloc1.(FunctionCall).getTarget().hasGlobalName("MmFreePagesFromMdl") and
158+
isExFreePoolCall(_, e)
159+
}
160+
161+
module UseAfterFreeParam implements FlowFromFreeParamSig {
162+
predicate isSink = isUse/2;
163+
164+
predicate isExcluded = isExcludeFreeUsePair/2;
165+
166+
predicate sourceSinkIsRelated = defaultSourceSinkIsRelated/2;
167+
}
168+
169+
import UseAfterFreeParam

0 commit comments

Comments
 (0)