Skip to content

Commit 145b5a3

Browse files
authored
Merge pull request #15343 from microsoft/38-cpp-generalize-use-after-free-libraries
Generalization of FlowAfterFree
2 parents b1b236d + 55fe8d3 commit 145b5a3

File tree

6 files changed

+256
-207
lines changed

6 files changed

+256
-207
lines changed

cpp/ql/lib/semmle/code/cpp/models/implementations/Deallocation.qll

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,12 @@ private class StandardDeallocationFunction extends DeallocationFunction {
2727
or
2828
this.hasGlobalOrStdName([
2929
// --- Windows Memory Management for Windows Drivers
30-
"ExFreePoolWithTag", "ExDeleteTimer", "IoFreeMdl", "IoFreeWorkItem", "IoFreeErrorLogEntry",
31-
"MmFreeContiguousMemory", "MmFreeContiguousMemorySpecifyCache", "MmFreeNonCachedMemory",
32-
"MmFreeMappingAddress", "MmFreePagesFromMdl", "MmUnmapReservedMapping",
33-
"MmUnmapLockedPages",
30+
"ExFreePool", "ExFreePoolWithTag", "ExDeleteTimer", "IoFreeIrp", "IoFreeMdl",
31+
"IoFreeErrorLogEntry", "IoFreeWorkItem", "MmFreeContiguousMemory",
32+
"MmFreeContiguousMemorySpecifyCache", "MmFreeNonCachedMemory", "MmFreeMappingAddress",
33+
"MmFreePagesFromMdl", "MmUnmapReservedMapping", "MmUnmapLockedPages",
34+
"NdisFreeGenericObject", "NdisFreeMemory", "NdisFreeMemoryWithTag", "NdisFreeMdl",
35+
"NdisFreeNetBufferListPool", "NdisFreeNetBufferPool",
3436
// --- Windows Global / Local legacy allocation
3537
"LocalFree", "GlobalFree", "LocalReAlloc", "GlobalReAlloc",
3638
// --- Windows System Services allocation
@@ -47,6 +49,7 @@ private class StandardDeallocationFunction extends DeallocationFunction {
4749
this.hasGlobalOrStdName([
4850
// --- Windows Memory Management for Windows Drivers
4951
"ExFreeToLookasideListEx", "ExFreeToPagedLookasideList", "ExFreeToNPagedLookasideList",
52+
"NdisFreeMemoryWithTagPriority", "StorPortFreeMdl", "StorPortFreePool",
5053
// --- NetBSD pool manager
5154
"pool_put", "pool_cache_put"
5255
]) and

cpp/ql/src/Critical/FlowAfterFree.qll renamed to cpp/ql/lib/semmle/code/cpp/security/flowafterfree/FlowAfterFree.qll

Lines changed: 70 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
1-
import cpp
2-
import semmle.code.cpp.dataflow.new.DataFlow
3-
private import semmle.code.cpp.ir.IR
4-
51
/**
6-
* Signature for a predicate that holds if `n.asExpr() = e` and `n` is a sink in
7-
* the `FlowFromFreeConfig` module.
2+
* General library for finding flow from a pointer being freed to a user-specified sink
83
*/
9-
private signature predicate isSinkSig(DataFlow::Node n, Expr e);
104

11-
/**
12-
* Holds if `dealloc` is a deallocation expression and `e` is an expression such
13-
* that `isFree(_, e)` holds for some `isFree` predicate satisfying `isSinkSig`,
14-
* and this source-sink pair should be excluded from the analysis.
15-
*/
16-
bindingset[dealloc, e]
17-
private signature predicate isExcludedSig(DeallocationExpr dealloc, Expr e);
5+
import cpp
6+
import semmle.code.cpp.dataflow.new.DataFlow
7+
private import semmle.code.cpp.ir.IR
188

199
/**
2010
* Holds if `(b1, i1)` strictly post-dominates `(b2, i2)`
@@ -38,6 +28,31 @@ predicate strictlyDominates(IRBlock b1, int i1, IRBlock b2, int i2) {
3828
b1.strictlyDominates(b2)
3929
}
4030

31+
/**
32+
* The signature for a module that is used to specify the inputs to the `FlowFromFree` module.
33+
*/
34+
signature module FlowFromFreeParamSig {
35+
/**
36+
* Holds if `n.asExpr() = e` and `n` is a sink in the `FlowFromFreeConfig`
37+
* module.
38+
*/
39+
predicate isSink(DataFlow::Node n, Expr e);
40+
41+
/**
42+
* Holds if `dealloc` is a deallocation expression and `e` is an expression such
43+
* that `isFree(_, e)` holds for some `isFree` predicate satisfying `isSinkSig`,
44+
* and this source-sink pair should be excluded from the analysis.
45+
*/
46+
bindingset[dealloc, e]
47+
predicate isExcluded(DeallocationExpr dealloc, Expr e);
48+
49+
/**
50+
* Holds if `sink` should be considered a `sink` when the source of flow is `source`.
51+
*/
52+
bindingset[source, sink]
53+
default predicate sourceSinkIsRelated(DataFlow::Node source, DataFlow::Node sink) { any() }
54+
}
55+
4156
/**
4257
* Constructs a `FlowFromFreeConfig` module that can be used to find flow between
4358
* a pointer being freed by some deallocation function, and a user-specified sink.
@@ -47,8 +62,8 @@ predicate strictlyDominates(IRBlock b1, int i1, IRBlock b2, int i2) {
4762
* 1. The source dominates the sink, or
4863
* 2. The sink post-dominates the source.
4964
*/
50-
module FlowFromFree<isSinkSig/2 isASink, isExcludedSig/2 isExcluded> {
51-
module FlowFromFreeConfig implements DataFlow::StateConfigSig {
65+
module FlowFromFree<FlowFromFreeParamSig P> {
66+
private module FlowFromFreeConfig implements DataFlow::StateConfigSig {
5267
class FlowState instanceof Expr {
5368
FlowState() { isFree(_, _, this, _) }
5469

@@ -59,20 +74,12 @@ module FlowFromFree<isSinkSig/2 isASink, isExcludedSig/2 isExcluded> {
5974

6075
pragma[inline]
6176
predicate isSink(DataFlow::Node sink, FlowState state) {
62-
exists(
63-
Expr e, DataFlow::Node source, IRBlock b1, int i1, IRBlock b2, int i2,
64-
DeallocationExpr dealloc
65-
|
66-
isASink(sink, e) and
77+
exists(Expr e, DataFlow::Node source, DeallocationExpr dealloc |
78+
P::isSink(sink, e) and
6779
isFree(source, _, state, dealloc) and
6880
e != state and
69-
source.hasIndexInBlock(b1, i1) and
70-
sink.hasIndexInBlock(b2, i2) and
71-
not isExcluded(dealloc, e)
72-
|
73-
strictlyDominates(b1, i1, b2, i2)
74-
or
75-
strictlyPostDominates(b2, i2, b1, i1)
81+
not P::isExcluded(dealloc, e) and
82+
P::sourceSinkIsRelated(source, sink)
7683
)
7784
}
7885

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

cpp/ql/src/Critical/DoubleFree.ql

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
import cpp
1515
import semmle.code.cpp.dataflow.new.DataFlow
16-
import FlowAfterFree
16+
import semmle.code.cpp.security.flowafterfree.FlowAfterFree
1717
import DoubleFree::PathGraph
1818

1919
/**
@@ -22,26 +22,15 @@ import DoubleFree::PathGraph
2222
*/
2323
predicate isFree(DataFlow::Node n, Expr e) { isFree(_, n, e, _) }
2424

25-
/**
26-
* `dealloc1` is a deallocation expression and `e` is an expression such
27-
* that is deallocated by a deallocation expression, and the `(dealloc1, e)` pair
28-
* should be excluded by the `FlowFromFree` library.
29-
*
30-
* Note that `e` is not necessarily the expression deallocated by `dealloc1`. It will
31-
* be bound to the second deallocation as identified by the `FlowFromFree` library.
32-
*/
33-
bindingset[dealloc1, e]
34-
predicate isExcludeFreePair(DeallocationExpr dealloc1, Expr e) {
35-
exists(DeallocationExpr dealloc2 | isFree(_, _, e, dealloc2) |
36-
dealloc1.(FunctionCall).getTarget().hasGlobalName("MmFreePagesFromMdl") and
37-
// From https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-mmfreepagesfrommdl:
38-
// "After calling MmFreePagesFromMdl, the caller must also call ExFreePool
39-
// to release the memory that was allocated for the MDL structure."
40-
isExFreePoolCall(dealloc2, _)
41-
)
25+
module DoubleFreeParam implements FlowFromFreeParamSig {
26+
predicate isSink = isFree/2;
27+
28+
predicate isExcluded = isExcludedMmFreePageFromMdl/2;
29+
30+
predicate sourceSinkIsRelated = defaultSourceSinkIsRelated/2;
4231
}
4332

44-
module DoubleFree = FlowFromFree<isFree/2, isExcludeFreePair/2>;
33+
module DoubleFree = FlowFromFree<DoubleFreeParam>;
4534

4635
from DoubleFree::PathNode source, DoubleFree::PathNode sink, DeallocationExpr dealloc, Expr e2
4736
where

0 commit comments

Comments
 (0)