Skip to content

Commit 95b37a7

Browse files
authored
[analyzer] Detect leak of a stack address through output arguments 2/3 (#105653)
At this point, only functions called from other functions (i.e., not top-level) are covered. Top-level functions have a different exit sequence and will be handled by a subsequent change. CPP-4734 ------- This is the second of three commits constituting #105648 it must not be merged before #105652
1 parent 914fa67 commit 95b37a7

File tree

2 files changed

+75
-20
lines changed

2 files changed

+75
-20
lines changed

clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,23 @@ void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS,
288288
EmitStackError(C, R, RetE);
289289
}
290290

291+
static const MemSpaceRegion *getStackOrGlobalSpaceRegion(const MemRegion *R) {
292+
assert(R);
293+
if (const auto *MemSpace = R->getMemorySpace()) {
294+
if (const auto *SSR = MemSpace->getAs<StackSpaceRegion>())
295+
return SSR;
296+
if (const auto *GSR = MemSpace->getAs<GlobalsSpaceRegion>())
297+
return GSR;
298+
}
299+
// If R describes a lambda capture, it will be a symbolic region
300+
// referring to a field region of another symbolic region.
301+
if (const auto *SymReg = R->getBaseRegion()->getAs<SymbolicRegion>()) {
302+
if (const auto *OriginReg = SymReg->getSymbol()->getOriginRegion())
303+
return getStackOrGlobalSpaceRegion(OriginReg);
304+
}
305+
return nullptr;
306+
}
307+
291308
std::optional<std::string> printReferrer(const MemRegion *Referrer) {
292309
assert(Referrer);
293310
const StringRef ReferrerMemorySpace = [](const MemSpaceRegion *Space) {
@@ -297,20 +314,31 @@ std::optional<std::string> printReferrer(const MemRegion *Referrer) {
297314
return "global";
298315
assert(isa<StackSpaceRegion>(Space));
299316
return "stack";
300-
}(Referrer->getMemorySpace());
301-
302-
// We should really only have VarRegions here.
303-
// Anything else is really surprising, and we should get notified if such
304-
// ever happens.
305-
const auto *ReferrerVar = dyn_cast<VarRegion>(Referrer);
306-
if (!ReferrerVar) {
307-
assert(false && "We should have a VarRegion here");
308-
return std::nullopt; // Defensively skip this one.
317+
}(getStackOrGlobalSpaceRegion(Referrer));
318+
319+
while (!Referrer->canPrintPretty()) {
320+
if (const auto *SymReg = dyn_cast<SymbolicRegion>(Referrer);
321+
SymReg && SymReg->getSymbol()->getOriginRegion()) {
322+
Referrer = SymReg->getSymbol()->getOriginRegion()->getBaseRegion();
323+
} else if (isa<CXXThisRegion>(Referrer)) {
324+
// Skip members of a class, it is handled in CheckExprLifetime.cpp as
325+
// warn_bind_ref_member_to_parameter or
326+
// warn_init_ptr_member_to_parameter_addr
327+
return std::nullopt;
328+
} else {
329+
Referrer->dump();
330+
assert(false && "Unexpected referrer region type.");
331+
return std::nullopt;
332+
}
309333
}
310-
const std::string ReferrerVarName =
311-
ReferrerVar->getDecl()->getDeclName().getAsString();
334+
assert(Referrer);
335+
assert(Referrer->canPrintPretty());
312336

313-
return (ReferrerMemorySpace + " variable '" + ReferrerVarName + "'").str();
337+
std::string buf;
338+
llvm::raw_string_ostream os(buf);
339+
os << ReferrerMemorySpace << " variable ";
340+
Referrer->printPretty(os);
341+
return buf;
314342
}
315343

316344
void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
@@ -332,16 +360,20 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
332360
/// referred by an other stack variable from different stack frame.
333361
bool checkForDanglingStackVariable(const MemRegion *Referrer,
334362
const MemRegion *Referred) {
335-
const auto *ReferrerMemSpace =
336-
Referrer->getMemorySpace()->getAs<StackSpaceRegion>();
363+
const auto *ReferrerMemSpace = getStackOrGlobalSpaceRegion(Referrer);
337364
const auto *ReferredMemSpace =
338365
Referred->getMemorySpace()->getAs<StackSpaceRegion>();
339366

340367
if (!ReferrerMemSpace || !ReferredMemSpace)
341368
return false;
342369

370+
const auto *ReferrerStackSpace =
371+
ReferrerMemSpace->getAs<StackSpaceRegion>();
372+
if (!ReferrerStackSpace)
373+
return false;
374+
343375
if (ReferredMemSpace->getStackFrame() == PoppedFrame &&
344-
ReferrerMemSpace->getStackFrame()->isParentOf(PoppedFrame)) {
376+
ReferrerStackSpace->getStackFrame()->isParentOf(PoppedFrame)) {
345377
V.emplace_back(Referrer, Referred);
346378
return true;
347379
}
@@ -387,7 +419,7 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
387419
if (!BT_stackleak)
388420
BT_stackleak =
389421
std::make_unique<BugType>(CheckNames[CK_StackAddrEscapeChecker],
390-
"Stack address stored into global variable");
422+
"Stack address leaks outside of stack frame");
391423

392424
for (const auto &P : Cb.V) {
393425
const MemRegion *Referrer = P.first->getBaseRegion();

clang/test/Analysis/stack-addr-ps.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s -Wno-undefined-bool-conversion
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s -Wno-undefined-bool-conversion
22

33
typedef __INTPTR_TYPE__ intptr_t;
44

5+
template <typename T>
6+
void clang_analyzer_dump(T x);
7+
58
const int& g() {
69
int s;
710
return s; // expected-warning{{Address of stack memory associated with local variable 's' returned}} expected-warning{{reference to stack memory associated with local variable 's' returned}}
@@ -321,7 +324,7 @@ void param_ptr_to_ptr_to_ptr_top(void*** ppp) {
321324

322325
void param_ptr_to_ptr_to_ptr_callee(void*** ppp) {
323326
int local = 42;
324-
**ppp = &local; // no-warning FIXME
327+
**ppp = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'pp'}}
325328
}
326329

327330
void param_ptr_to_ptr_to_ptr_caller(void** pp) {
@@ -331,7 +334,7 @@ void param_ptr_to_ptr_to_ptr_caller(void** pp) {
331334
void lambda_to_context_ptr_to_ptr(int **pp) {
332335
auto lambda = [&] {
333336
int local = 42;
334-
*pp = &local; // no-warning FIXME
337+
*pp = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'pp'}}
335338
};
336339
lambda();
337340
(void)*pp;
@@ -734,7 +737,7 @@ void param_nested_and_transitive_top(NestedAndTransitive* nat) {
734737

735738
void param_nested_and_transitive_callee(NestedAndTransitive* nat) {
736739
int local = 42;
737-
*nat->next[2]->next[1]->p = &local; // no-warning FIXME
740+
*nat->next[2]->next[1]->p = &local; // expected-warning{{local variable 'local' is still referred to by the stack variable 'natCaller'}}
738741
}
739742

740743
void param_nested_and_transitive_caller(NestedAndTransitive natCaller) {
@@ -757,3 +760,23 @@ class CPtr {
757760
}
758761
};
759762
} // namespace leaking_as_member
763+
764+
namespace origin_region_limitation {
765+
void leaker(int ***leakerArg) {
766+
int local;
767+
clang_analyzer_dump(*leakerArg); // expected-warning{{&SymRegion{reg_$0<int ** arg>}}}
768+
// Incorrect message: 'arg', after it is reinitialized with value returned by 'tweak'
769+
// is no longer relevant.
770+
// The message must refer to 'original_arg' instead, but there is no easy way to
771+
// connect the SymRegion stored in 'original_arg' and 'original_arg' as variable.
772+
**leakerArg = &local; // expected-warning{{ 'local' is still referred to by the stack variable 'arg'}}
773+
}
774+
775+
int **tweak();
776+
777+
void foo(int **arg) {
778+
int **original_arg = arg;
779+
arg = tweak();
780+
leaker(&original_arg);
781+
}
782+
} // namespace origin_region_limitation

0 commit comments

Comments
 (0)