Skip to content

Commit 9df923a

Browse files
committed
C++: Catch more true positives by stepping into calls in the 'cpp/using-expired-stack-address' query.
1 parent eece222 commit 9df923a

File tree

3 files changed

+72
-31
lines changed

3 files changed

+72
-31
lines changed

cpp/ql/src/Likely Bugs/Memory Management/UsingExpiredStackAddress.ql

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,10 @@ predicate blockStoresToAddress(
178178
globalAddress = globalAddress(store.getDestinationAddress())
179179
}
180180

181-
predicate blockLoadsFromAddress(
182-
IRBlock block, int index, LoadInstruction load, TGlobalAddress globalAddress
183-
) {
184-
block.getInstruction(index) = load and
185-
globalAddress = globalAddress(load.getSourceAddress())
186-
}
187-
181+
/**
182+
* Holds if `globalAddress` evaluates to the address of `var` (which escaped through `store` before
183+
* returning through `call`) when control reaches `block`.
184+
*/
188185
predicate globalAddressPointsToStack(
189186
StoreInstruction store, StackVariable var, CallInstruction call, IRBlock block,
190187
TGlobalAddress globalAddress, boolean isCallBlock, boolean isStoreBlock
@@ -203,21 +200,55 @@ predicate globalAddressPointsToStack(
203200
)
204201
or
205202
isCallBlock = false and
206-
exists(IRBlock mid |
207-
mid.immediatelyDominates(block) and
208-
// Only recurse if there is no store to `globalAddress` in `mid`.
209-
globalAddressPointsToStack(store, var, call, mid, globalAddress, _, false)
203+
step(store, var, call, globalAddress, _, block)
204+
)
205+
}
206+
207+
pragma[inline]
208+
int getInstructionIndex(Instruction instr, IRBlock block) { block.getInstruction(result) = instr }
209+
210+
predicate step(
211+
StoreInstruction store, StackVariable var, CallInstruction call, TGlobalAddress globalAddress,
212+
IRBlock pred, IRBlock succ
213+
) {
214+
exists(boolean isCallBlock, boolean isStoreBlock |
215+
// Only recurse if there is no store to `globalAddress` in `mid`.
216+
globalAddressPointsToStack(store, var, call, pred, globalAddress, isCallBlock, isStoreBlock)
217+
|
218+
// Post domination ensures that `block` is always executed after `mid`
219+
// Domination ensures that `mid` is always executed before `block`
220+
isStoreBlock = false and
221+
succ.immediatelyPostDominates(pred) and
222+
pred.immediatelyDominates(succ)
223+
or
224+
exists(CallInstruction anotherCall, int anotherCallIndex |
225+
anotherCall = pred.getInstruction(anotherCallIndex) and
226+
succ.getFirstInstruction() instanceof EnterFunctionInstruction and
227+
succ.getEnclosingFunction() = anotherCall.getStaticCallTarget() and
228+
(if isCallBlock = true then getInstructionIndex(call, _) < anotherCallIndex else any()) and
229+
(
230+
if isStoreBlock = true
231+
then
232+
forex(int storeIndex | blockStoresToAddress(pred, storeIndex, _, globalAddress) |
233+
anotherCallIndex < storeIndex
234+
)
235+
else any()
236+
)
210237
)
211238
)
212239
}
213240

241+
predicate isSink(LoadInstruction load, IRBlock block, int index, TGlobalAddress globalAddress) {
242+
block.getInstruction(index) = load and
243+
globalAddress(load.getSourceAddress()) = globalAddress
244+
}
245+
214246
from
215247
StoreInstruction store, StackVariable var, LoadInstruction load, CallInstruction call,
216-
IRBlock block, boolean isCallBlock, TGlobalAddress address, boolean isStoreBlock
248+
IRBlock block, boolean isCallBlock, TGlobalAddress address, boolean isStoreBlock, int loadIndex
217249
where
218250
globalAddressPointsToStack(store, var, call, block, address, isCallBlock, isStoreBlock) and
219-
block.getAnInstruction() = load and
220-
globalAddress(load.getSourceAddress()) = address and
251+
isSink(load, block, loadIndex, address) and
221252
(
222253
// We know that we have a sequence:
223254
// (1) store to `address` -> (2) return from `f` -> (3) load from `address`.
@@ -226,22 +257,18 @@ where
226257
if isCallBlock = true
227258
then
228259
// If so, the load must happen after the call.
229-
exists(int callIndex, int loadIndex |
230-
blockLoadsFromAddress(_, loadIndex, load, _) and
231-
block.getInstruction(callIndex) = call and
232-
callIndex < loadIndex
233-
)
260+
getInstructionIndex(call, _) < loadIndex
234261
else any()
235262
) and
236-
// If there is a store to the address we need to make sure that the load we found was
237-
// before that store (So that the load doesn't read an overwritten value).
238-
if isStoreBlock = true
239-
then
240-
exists(int storeIndex, int loadIndex |
241-
blockStoresToAddress(block, storeIndex, _, address) and
242-
block.getInstruction(loadIndex) = load and
243-
loadIndex < storeIndex
244-
)
245-
else any()
263+
(
264+
// If there is a store to the address we need to make sure that the load we found was
265+
// before that store (So that the load doesn't read an overwritten value).
266+
if isStoreBlock = true
267+
then
268+
forex(int storeIndex | blockStoresToAddress(block, storeIndex, _, address) |
269+
loadIndex < storeIndex
270+
)
271+
else any()
272+
)
246273
select load, "Stack variable $@ escapes $@ and is used after it has expired.", var, var.toString(),
247274
store, "here"

cpp/ql/test/query-tests/Likely Bugs/Memory Management/UsingExpiredStackAddress/UsingExpiredStackAddress.expected

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
| test.cpp:15:16:15:16 | Load: p | Stack variable $@ escapes $@ and is used after it has expired. | test.cpp:9:7:9:7 | x | x | test.cpp:10:3:10:13 | Store: ... = ... | here |
2+
| test.cpp:24:16:24:16 | Load: p | Stack variable $@ escapes $@ and is used after it has expired. | test.cpp:9:7:9:7 | x | x | test.cpp:10:3:10:13 | Store: ... = ... | here |
23
| test.cpp:58:16:58:16 | Load: p | Stack variable $@ escapes $@ and is used after it has expired. | test.cpp:51:36:51:36 | y | y | test.cpp:52:3:52:13 | Store: ... = ... | here |
34
| test.cpp:73:16:73:16 | Load: p | Stack variable $@ escapes $@ and is used after it has expired. | test.cpp:62:7:62:7 | x | x | test.cpp:68:3:68:13 | Store: ... = ... | here |
45
| test.cpp:98:15:98:15 | Load: p | Stack variable $@ escapes $@ and is used after it has expired. | test.cpp:92:8:92:8 | s | s | test.cpp:93:3:93:15 | Store: ... = ... | here |

cpp/ql/test/query-tests/Likely Bugs/Memory Management/UsingExpiredStackAddress/test.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ int simple_field_good() {
2121
}
2222

2323
int deref_p() {
24-
return *s101.p;
24+
return *s101.p; // BAD
2525
}
2626

2727
int field_indirect_bad() {
2828
escape1();
29-
return deref_p(); // BAD [NOT DETECTED]
29+
return deref_p();
3030
}
3131

3232
int deref_i() {
@@ -197,3 +197,16 @@ void test_not_escape_through_array() {
197197
int x21 = s1.a2[0][1]; // GOOD
198198
int* x22 = s1.a3[5][2]; // GOOD
199199
}
200+
201+
int maybe_deref_p(bool b) {
202+
if(b) {
203+
return *s101.p; // GOOD
204+
} else {
205+
return 0;
206+
}
207+
}
208+
209+
int field_indirect_maybe_bad(bool b) {
210+
escape1();
211+
return maybe_deref_p(b);
212+
}

0 commit comments

Comments
 (0)