1 //=== StackAddrEscapeChecker.cpp ----------------------------------*- C++ -*--//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // This file defines stack address leak checker, which checks if an invalid
10 // stack address is stored into a global or heap location. See CERT DCL30-C.
12 //===----------------------------------------------------------------------===//
14 #include "clang/AST/ExprCXX.h"
15 #include "clang/Basic/SourceManager.h"
16 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
17 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
18 #include "clang/StaticAnalyzer/Core/Checker.h"
19 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
20 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
21 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
22 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
23 #include "llvm/ADT/SmallString.h"
24 #include "llvm/Support/raw_ostream.h"
25 using namespace clang
;
29 class StackAddrEscapeChecker
30 : public Checker
<check::PreCall
, check::PreStmt
<ReturnStmt
>,
32 mutable IdentifierInfo
*dispatch_semaphore_tII
= nullptr;
33 mutable std::unique_ptr
<BugType
> BT_stackleak
;
34 mutable std::unique_ptr
<BugType
> BT_returnstack
;
35 mutable std::unique_ptr
<BugType
> BT_capturedstackasync
;
36 mutable std::unique_ptr
<BugType
> BT_capturedstackret
;
40 CK_StackAddrEscapeChecker
,
41 CK_StackAddrAsyncEscapeChecker
,
45 bool ChecksEnabled
[CK_NumCheckKinds
] = {false};
46 CheckerNameRef CheckNames
[CK_NumCheckKinds
];
48 void checkPreCall(const CallEvent
&Call
, CheckerContext
&C
) const;
49 void checkPreStmt(const ReturnStmt
*RS
, CheckerContext
&C
) const;
50 void checkEndFunction(const ReturnStmt
*RS
, CheckerContext
&Ctx
) const;
53 void checkReturnedBlockCaptures(const BlockDataRegion
&B
,
54 CheckerContext
&C
) const;
55 void checkAsyncExecutedBlockCaptures(const BlockDataRegion
&B
,
56 CheckerContext
&C
) const;
57 void EmitStackError(CheckerContext
&C
, const MemRegion
*R
,
58 const Expr
*RetE
) const;
59 bool isSemaphoreCaptured(const BlockDecl
&B
) const;
60 static SourceRange
genName(raw_ostream
&os
, const MemRegion
*R
,
62 static SmallVector
<const MemRegion
*, 4>
63 getCapturedStackRegions(const BlockDataRegion
&B
, CheckerContext
&C
);
64 static bool isNotInCurrentFrame(const MemRegion
*R
, CheckerContext
&C
);
68 SourceRange
StackAddrEscapeChecker::genName(raw_ostream
&os
, const MemRegion
*R
,
70 // Get the base region, stripping away fields and elements.
71 R
= R
->getBaseRegion();
72 SourceManager
&SM
= Ctx
.getSourceManager();
76 // Check if the region is a compound literal.
77 if (const auto *CR
= dyn_cast
<CompoundLiteralRegion
>(R
)) {
78 const CompoundLiteralExpr
*CL
= CR
->getLiteralExpr();
79 os
<< "stack memory associated with a compound literal "
81 << SM
.getExpansionLineNumber(CL
->getBeginLoc()) << " returned to caller";
82 range
= CL
->getSourceRange();
83 } else if (const auto *AR
= dyn_cast
<AllocaRegion
>(R
)) {
84 const Expr
*ARE
= AR
->getExpr();
85 SourceLocation L
= ARE
->getBeginLoc();
86 range
= ARE
->getSourceRange();
87 os
<< "stack memory allocated by call to alloca() on line "
88 << SM
.getExpansionLineNumber(L
);
89 } else if (const auto *BR
= dyn_cast
<BlockDataRegion
>(R
)) {
90 const BlockDecl
*BD
= BR
->getCodeRegion()->getDecl();
91 SourceLocation L
= BD
->getBeginLoc();
92 range
= BD
->getSourceRange();
93 os
<< "stack-allocated block declared on line "
94 << SM
.getExpansionLineNumber(L
);
95 } else if (const auto *VR
= dyn_cast
<VarRegion
>(R
)) {
96 os
<< "stack memory associated with local variable '" << VR
->getString()
98 range
= VR
->getDecl()->getSourceRange();
99 } else if (const auto *LER
= dyn_cast
<CXXLifetimeExtendedObjectRegion
>(R
)) {
100 QualType Ty
= LER
->getValueType().getLocalUnqualifiedType();
101 os
<< "stack memory associated with temporary object of type '";
102 Ty
.print(os
, Ctx
.getPrintingPolicy());
103 os
<< "' lifetime extended by local variable";
104 if (const IdentifierInfo
*ID
= LER
->getExtendingDecl()->getIdentifier())
105 os
<< " '" << ID
->getName() << '\'';
106 range
= LER
->getExpr()->getSourceRange();
107 } else if (const auto *TOR
= dyn_cast
<CXXTempObjectRegion
>(R
)) {
108 QualType Ty
= TOR
->getValueType().getLocalUnqualifiedType();
109 os
<< "stack memory associated with temporary object of type '";
110 Ty
.print(os
, Ctx
.getPrintingPolicy());
112 range
= TOR
->getExpr()->getSourceRange();
114 llvm_unreachable("Invalid region in ReturnStackAddressChecker.");
120 bool StackAddrEscapeChecker::isNotInCurrentFrame(const MemRegion
*R
,
122 const StackSpaceRegion
*S
= cast
<StackSpaceRegion
>(R
->getMemorySpace());
123 return S
->getStackFrame() != C
.getStackFrame();
126 bool StackAddrEscapeChecker::isSemaphoreCaptured(const BlockDecl
&B
) const {
127 if (!dispatch_semaphore_tII
)
128 dispatch_semaphore_tII
= &B
.getASTContext().Idents
.get("dispatch_semaphore_t");
129 for (const auto &C
: B
.captures()) {
130 const auto *T
= C
.getVariable()->getType()->getAs
<TypedefType
>();
131 if (T
&& T
->getDecl()->getIdentifier() == dispatch_semaphore_tII
)
137 SmallVector
<const MemRegion
*, 4>
138 StackAddrEscapeChecker::getCapturedStackRegions(const BlockDataRegion
&B
,
140 SmallVector
<const MemRegion
*, 4> Regions
;
141 for (auto Var
: B
.referenced_vars()) {
142 SVal Val
= C
.getState()->getSVal(Var
.getCapturedRegion());
143 const MemRegion
*Region
= Val
.getAsRegion();
144 if (Region
&& isa
<StackSpaceRegion
>(Region
->getMemorySpace()))
145 Regions
.push_back(Region
);
150 void StackAddrEscapeChecker::EmitStackError(CheckerContext
&C
,
152 const Expr
*RetE
) const {
153 ExplodedNode
*N
= C
.generateNonFatalErrorNode();
157 BT_returnstack
= std::make_unique
<BugType
>(
158 CheckNames
[CK_StackAddrEscapeChecker
],
159 "Return of address to stack-allocated memory");
160 // Generate a report for this bug.
161 SmallString
<128> buf
;
162 llvm::raw_svector_ostream
os(buf
);
163 SourceRange range
= genName(os
, R
, C
.getASTContext());
164 os
<< " returned to caller";
166 std::make_unique
<PathSensitiveBugReport
>(*BT_returnstack
, os
.str(), N
);
167 report
->addRange(RetE
->getSourceRange());
169 report
->addRange(range
);
170 C
.emitReport(std::move(report
));
173 void StackAddrEscapeChecker::checkAsyncExecutedBlockCaptures(
174 const BlockDataRegion
&B
, CheckerContext
&C
) const {
175 // There is a not-too-uncommon idiom
176 // where a block passed to dispatch_async captures a semaphore
177 // and then the thread (which called dispatch_async) is blocked on waiting
178 // for the completion of the execution of the block
179 // via dispatch_semaphore_wait. To avoid false-positives (for now)
180 // we ignore all the blocks which have captured
181 // a variable of the type "dispatch_semaphore_t".
182 if (isSemaphoreCaptured(*B
.getDecl()))
184 for (const MemRegion
*Region
: getCapturedStackRegions(B
, C
)) {
185 // The block passed to dispatch_async may capture another block
186 // created on the stack. However, there is no leak in this situaton,
187 // no matter if ARC or no ARC is enabled:
188 // dispatch_async copies the passed "outer" block (via Block_copy)
189 // and if the block has captured another "inner" block,
190 // the "inner" block will be copied as well.
191 if (isa
<BlockDataRegion
>(Region
))
193 ExplodedNode
*N
= C
.generateNonFatalErrorNode();
196 if (!BT_capturedstackasync
)
197 BT_capturedstackasync
= std::make_unique
<BugType
>(
198 CheckNames
[CK_StackAddrAsyncEscapeChecker
],
199 "Address of stack-allocated memory is captured");
200 SmallString
<128> Buf
;
201 llvm::raw_svector_ostream
Out(Buf
);
202 SourceRange Range
= genName(Out
, Region
, C
.getASTContext());
203 Out
<< " is captured by an asynchronously-executed block";
204 auto Report
= std::make_unique
<PathSensitiveBugReport
>(
205 *BT_capturedstackasync
, Out
.str(), N
);
207 Report
->addRange(Range
);
208 C
.emitReport(std::move(Report
));
212 void StackAddrEscapeChecker::checkReturnedBlockCaptures(
213 const BlockDataRegion
&B
, CheckerContext
&C
) const {
214 for (const MemRegion
*Region
: getCapturedStackRegions(B
, C
)) {
215 if (isNotInCurrentFrame(Region
, C
))
217 ExplodedNode
*N
= C
.generateNonFatalErrorNode();
220 if (!BT_capturedstackret
)
221 BT_capturedstackret
= std::make_unique
<BugType
>(
222 CheckNames
[CK_StackAddrEscapeChecker
],
223 "Address of stack-allocated memory is captured");
224 SmallString
<128> Buf
;
225 llvm::raw_svector_ostream
Out(Buf
);
226 SourceRange Range
= genName(Out
, Region
, C
.getASTContext());
227 Out
<< " is captured by a returned block";
228 auto Report
= std::make_unique
<PathSensitiveBugReport
>(*BT_capturedstackret
,
231 Report
->addRange(Range
);
232 C
.emitReport(std::move(Report
));
236 void StackAddrEscapeChecker::checkPreCall(const CallEvent
&Call
,
237 CheckerContext
&C
) const {
238 if (!ChecksEnabled
[CK_StackAddrAsyncEscapeChecker
])
240 if (!Call
.isGlobalCFunction("dispatch_after") &&
241 !Call
.isGlobalCFunction("dispatch_async"))
243 for (unsigned Idx
= 0, NumArgs
= Call
.getNumArgs(); Idx
< NumArgs
; ++Idx
) {
244 if (const BlockDataRegion
*B
= dyn_cast_or_null
<BlockDataRegion
>(
245 Call
.getArgSVal(Idx
).getAsRegion()))
246 checkAsyncExecutedBlockCaptures(*B
, C
);
250 void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt
*RS
,
251 CheckerContext
&C
) const {
252 if (!ChecksEnabled
[CK_StackAddrEscapeChecker
])
255 const Expr
*RetE
= RS
->getRetValue();
258 RetE
= RetE
->IgnoreParens();
260 SVal V
= C
.getSVal(RetE
);
261 const MemRegion
*R
= V
.getAsRegion();
265 if (const BlockDataRegion
*B
= dyn_cast
<BlockDataRegion
>(R
))
266 checkReturnedBlockCaptures(*B
, C
);
268 if (!isa
<StackSpaceRegion
>(R
->getMemorySpace()) || isNotInCurrentFrame(R
, C
))
271 // Returning a record by value is fine. (In this case, the returned
272 // expression will be a copy-constructor, possibly wrapped in an
273 // ExprWithCleanups node.)
274 if (const ExprWithCleanups
*Cleanup
= dyn_cast
<ExprWithCleanups
>(RetE
))
275 RetE
= Cleanup
->getSubExpr();
276 if (isa
<CXXConstructExpr
>(RetE
) && RetE
->getType()->isRecordType())
279 // The CK_CopyAndAutoreleaseBlockObject cast causes the block to be copied
280 // so the stack address is not escaping here.
281 if (const auto *ICE
= dyn_cast
<ImplicitCastExpr
>(RetE
)) {
282 if (isa
<BlockDataRegion
>(R
) &&
283 ICE
->getCastKind() == CK_CopyAndAutoreleaseBlockObject
) {
288 EmitStackError(C
, R
, RetE
);
291 void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt
*RS
,
292 CheckerContext
&Ctx
) const {
293 if (!ChecksEnabled
[CK_StackAddrEscapeChecker
])
296 ProgramStateRef State
= Ctx
.getState();
298 // Iterate over all bindings to global variables and see if it contains
299 // a memory region in the stack space.
300 class CallBack
: public StoreManager::BindingsHandler
{
303 const StackFrameContext
*PoppedFrame
;
305 /// Look for stack variables referring to popped stack variables.
306 /// Returns true only if it found some dangling stack variables
307 /// referred by an other stack variable from different stack frame.
308 bool checkForDanglingStackVariable(const MemRegion
*Referrer
,
309 const MemRegion
*Referred
) {
310 const auto *ReferrerMemSpace
=
311 Referrer
->getMemorySpace()->getAs
<StackSpaceRegion
>();
312 const auto *ReferredMemSpace
=
313 Referred
->getMemorySpace()->getAs
<StackSpaceRegion
>();
315 if (!ReferrerMemSpace
|| !ReferredMemSpace
)
318 const auto *ReferrerFrame
= ReferrerMemSpace
->getStackFrame();
319 const auto *ReferredFrame
= ReferredMemSpace
->getStackFrame();
321 if (ReferrerMemSpace
&& ReferredMemSpace
) {
322 if (ReferredFrame
== PoppedFrame
&&
323 ReferrerFrame
->isParentOf(PoppedFrame
)) {
324 V
.emplace_back(Referrer
, Referred
);
332 SmallVector
<std::pair
<const MemRegion
*, const MemRegion
*>, 10> V
;
334 CallBack(CheckerContext
&CC
) : Ctx(CC
), PoppedFrame(CC
.getStackFrame()) {}
336 bool HandleBinding(StoreManager
&SMgr
, Store S
, const MemRegion
*Region
,
338 const MemRegion
*VR
= Val
.getAsRegion();
342 if (checkForDanglingStackVariable(Region
, VR
))
345 // Check the globals for the same.
346 if (!isa
<GlobalsSpaceRegion
>(Region
->getMemorySpace()))
348 if (VR
&& VR
->hasStackStorage() && !isNotInCurrentFrame(VR
, Ctx
))
349 V
.emplace_back(Region
, VR
);
355 State
->getStateManager().getStoreManager().iterBindings(State
->getStore(),
361 // Generate an error node.
362 ExplodedNode
*N
= Ctx
.generateNonFatalErrorNode(State
);
368 std::make_unique
<BugType
>(CheckNames
[CK_StackAddrEscapeChecker
],
369 "Stack address stored into global variable");
371 for (const auto &P
: Cb
.V
) {
372 const MemRegion
*Referrer
= P
.first
->getBaseRegion();
373 const MemRegion
*Referred
= P
.second
;
375 // Generate a report for this bug.
376 const StringRef CommonSuffix
=
377 "upon returning to the caller. This will be a dangling reference";
378 SmallString
<128> Buf
;
379 llvm::raw_svector_ostream
Out(Buf
);
380 const SourceRange Range
= genName(Out
, Referred
, Ctx
.getASTContext());
382 if (isa
<CXXTempObjectRegion
, CXXLifetimeExtendedObjectRegion
>(Referrer
)) {
383 Out
<< " is still referred to by a temporary object on the stack "
386 std::make_unique
<PathSensitiveBugReport
>(*BT_stackleak
, Out
.str(), N
);
388 Report
->addRange(Range
);
389 Ctx
.emitReport(std::move(Report
));
393 const StringRef ReferrerMemorySpace
= [](const MemSpaceRegion
*Space
) {
394 if (isa
<StaticGlobalSpaceRegion
>(Space
))
396 if (isa
<GlobalsSpaceRegion
>(Space
))
398 assert(isa
<StackSpaceRegion
>(Space
));
400 }(Referrer
->getMemorySpace());
402 // We should really only have VarRegions here.
403 // Anything else is really surprising, and we should get notified if such
405 const auto *ReferrerVar
= dyn_cast
<VarRegion
>(Referrer
);
407 assert(false && "We should have a VarRegion here");
408 continue; // Defensively skip this one.
410 const std::string ReferrerVarName
=
411 ReferrerVar
->getDecl()->getDeclName().getAsString();
413 Out
<< " is still referred to by the " << ReferrerMemorySpace
414 << " variable '" << ReferrerVarName
<< "' " << CommonSuffix
;
416 std::make_unique
<PathSensitiveBugReport
>(*BT_stackleak
, Out
.str(), N
);
418 Report
->addRange(Range
);
420 Ctx
.emitReport(std::move(Report
));
424 void ento::registerStackAddrEscapeBase(CheckerManager
&mgr
) {
425 mgr
.registerChecker
<StackAddrEscapeChecker
>();
428 bool ento::shouldRegisterStackAddrEscapeBase(const CheckerManager
&mgr
) {
432 #define REGISTER_CHECKER(name) \
433 void ento::register##name(CheckerManager &Mgr) { \
434 StackAddrEscapeChecker *Chk = Mgr.getChecker<StackAddrEscapeChecker>(); \
435 Chk->ChecksEnabled[StackAddrEscapeChecker::CK_##name] = true; \
436 Chk->CheckNames[StackAddrEscapeChecker::CK_##name] = \
437 Mgr.getCurrentCheckerName(); \
440 bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; }
442 REGISTER_CHECKER(StackAddrEscapeChecker
)
443 REGISTER_CHECKER(StackAddrAsyncEscapeChecker
)