Skip to content

Commit 9d5b8cf

Browse files
committed
C++: Add a barrier to the 'cpp/invalid-pointer-deref' query.
1 parent fba753c commit 9d5b8cf

File tree

1 file changed

+100
-11
lines changed

1 file changed

+100
-11
lines changed

cpp/ql/src/experimental/Security/CWE/CWE-193/InvalidPointerDeref.ql

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import cpp
1919
import semmle.code.cpp.ir.dataflow.internal.ProductFlow
2020
import semmle.code.cpp.rangeanalysis.new.internal.semantic.analysis.RangeAnalysis
2121
import semmle.code.cpp.rangeanalysis.new.internal.semantic.SemanticExprSpecific
22+
import semmle.code.cpp.ir.ValueNumbering
23+
import semmle.code.cpp.controlflow.IRGuards
2224
import semmle.code.cpp.ir.IR
2325
import codeql.util.Unit
2426

@@ -67,6 +69,84 @@ predicate hasSize(HeuristicAllocationExpr alloc, DataFlow::Node n, int state) {
6769
)
6870
}
6971

72+
/**
73+
* A module that encapsulates a barrier guard to remove false positives from flow like:
74+
* ```cpp
75+
* char *p = new char[size];
76+
* // ...
77+
* unsigned n = size;
78+
* // ...
79+
* if(n < size) {
80+
* use(*p[n]);
81+
* }
82+
* ```
83+
* In this case, the sink pair identified by the product flow library (without any additional barriers)
84+
* would be `(p, n)` (where `n` is the `n` in `p[n]`), because there exists a pointer-arithmetic
85+
* instruction `pai` such that:
86+
* 1. The left-hand of `pai` flows from the allocation, and
87+
* 2. The right-hand of `pai` is non-strictly upper bounded by `n` (where `n` is the `n` in `p[n]`)
88+
* but because there's a strict comparison that compares `n` against the size of the allocation this
89+
* snippet is fine.
90+
*/
91+
module Barrier2 {
92+
private class FlowState2 = AllocToInvalidPointerConfig::FlowState2;
93+
94+
private module BarrierConfig2 implements DataFlow::ConfigSig {
95+
predicate isSource(DataFlow::Node source) {
96+
// The sources is the same as in the sources for the second
97+
// projection in the `AllocToInvalidPointerConfig` module.
98+
hasSize(_, source, _)
99+
}
100+
101+
additional predicate isSink(
102+
DataFlow::Node left, DataFlow::Node right, IRGuardCondition g, FlowState2 state,
103+
boolean testIsTrue
104+
) {
105+
// The sink is any "large" side of a relational comparison.
106+
g.comparesLt(left.asOperand(), right.asOperand(), state, true, testIsTrue)
107+
}
108+
109+
predicate isSink(DataFlow::Node sink) { isSink(_, sink, _, _, _) }
110+
}
111+
112+
private import DataFlow::Global<BarrierConfig2>
113+
114+
private FlowState2 getAFlowStateForNode(DataFlow::Node node) {
115+
exists(DataFlow::Node source |
116+
flow(source, node) and
117+
hasSize(_, source, result)
118+
)
119+
}
120+
121+
private predicate operandGuardChecks(
122+
IRGuardCondition g, Operand left, Operand right, FlowState2 state, boolean edge
123+
) {
124+
exists(DataFlow::Node nLeft, DataFlow::Node nRight, FlowState2 state0 |
125+
nRight.asOperand() = right and
126+
nLeft.asOperand() = left and
127+
BarrierConfig2::isSink(nLeft, nRight, g, state0, edge) and
128+
state = getAFlowStateForNode(nRight) and
129+
state0 <= state
130+
)
131+
}
132+
133+
Instruction getABarrierInstruction(FlowState2 state) {
134+
exists(IRGuardCondition g, Operand right, ValueNumber value, boolean edge |
135+
operandGuardChecks(g, value.getAUse(), right, state, edge) and
136+
result = value.getAnInstruction() and
137+
g.controls(result.getBlock(), edge)
138+
)
139+
}
140+
141+
DataFlow::Node getABarrierNode(FlowState2 state) {
142+
result.asOperand() = getABarrierInstruction(state).getAUse()
143+
}
144+
145+
IRBlock getABarrierBlock(FlowState2 state) {
146+
result.getAnInstruction() = getABarrierInstruction(state)
147+
}
148+
}
149+
70150
/**
71151
* A product-flow configuration for flow from an (allocation, size) pair to a
72152
* pointer-arithmetic operation that is non-strictly upper-bounded by `allocation + size`.
@@ -111,15 +191,14 @@ module AllocToInvalidPointerConfig implements ProductFlow::StateConfigSig {
111191
exists(state1) and
112192
// We check that the delta computed by the range analysis matches the
113193
// state value that we set in `isSourcePair`.
114-
exists(int delta |
115-
isSinkImpl(_, sink1, sink2, delta) and
116-
state2 = delta
117-
)
194+
isSinkImpl(_, sink1, sink2, state2)
118195
}
119196

120197
predicate isBarrier1(DataFlow::Node node, FlowState1 state) { none() }
121198

122-
predicate isBarrier2(DataFlow::Node node, FlowState2 state) { none() }
199+
predicate isBarrier2(DataFlow::Node node, FlowState2 state) {
200+
node = Barrier2::getABarrierNode(state)
201+
}
123202

124203
predicate isBarrierIn1(DataFlow::Node node) { isSourcePair(node, _, _, _) }
125204

@@ -163,7 +242,8 @@ predicate pointerAddInstructionHasBounds(
163242
exists(Instruction right |
164243
pai.getRight() = right and
165244
pai.getLeft() = sink1.asInstruction() and
166-
bounded1(right, sink2.asInstruction(), delta)
245+
bounded1(right, sink2.asInstruction(), delta) and
246+
not [right, sink2.asInstruction()] = Barrier2::getABarrierInstruction(delta)
167247
)
168248
}
169249

@@ -246,12 +326,21 @@ module InvalidPointerToDerefFlow = DataFlow::Global<InvalidPointerToDerefConfig>
246326
predicate invalidPointerToDerefSource(
247327
PointerArithmeticInstruction pai, DataFlow::Node source, int delta
248328
) {
249-
exists(AllocToInvalidPointerFlow::PathNode1 p, DataFlow::Node sink1 |
250-
pragma[only_bind_out](p.getNode()) = sink1 and
251-
AllocToInvalidPointerFlow::flowPath(_, _, pragma[only_bind_into](p), _) and
252-
isSinkImpl(pai, sink1, _, _) and
329+
exists(
330+
AllocToInvalidPointerFlow::PathNode1 p1, AllocToInvalidPointerFlow::PathNode2 p2,
331+
DataFlow::Node sink1, DataFlow::Node sink2, int delta0
332+
|
333+
pragma[only_bind_out](p1.getNode()) = sink1 and
334+
pragma[only_bind_out](p2.getNode()) = sink2 and
335+
AllocToInvalidPointerFlow::flowPath(_, _, pragma[only_bind_into](p1), pragma[only_bind_into](p2)) and
336+
// Note that `delta` is not necessarily equal to `delta0`:
337+
// `delta0` is the constant offset added to the size of the allocation, and
338+
// delta is the constant difference between the pointer-arithmetic instruction
339+
// and the instruction computing the address for which we will search for a dereference.
340+
isSinkImpl(pai, sink1, sink2, delta0) and
253341
bounded2(source.asInstruction(), pai, delta) and
254-
delta >= 0
342+
delta >= 0 and
343+
not source.getBasicBlock() = Barrier2::getABarrierBlock(delta0)
255344
)
256345
}
257346

0 commit comments

Comments
 (0)