clang: lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp Source File (original) (raw)
1
2
3
4
5
6
7
8
9
10
11
12
13
23#include "llvm/ADT/SmallString.h"
24#include "llvm/Support/raw_ostream.h"
25using namespace clang;
26using namespace ento;
27
28namespace {
29class StackAddrEscapeChecker
30 : public Checker<check::PreCall, check::PreStmt,
31 check::EndFunction> {
32 mutable IdentifierInfo *dispatch_semaphore_tII = nullptr;
33 mutable std::unique_ptr BT_stackleak;
34 mutable std::unique_ptr BT_returnstack;
35 mutable std::unique_ptr BT_capturedstackasync;
36 mutable std::unique_ptr BT_capturedstackret;
37
38public:
39 enum CheckKind {
40 CK_StackAddrEscapeChecker,
41 CK_StackAddrAsyncEscapeChecker,
42 CK_NumCheckKinds
43 };
44
45 bool ChecksEnabled[CK_NumCheckKinds] = {false};
47
51
52private:
55 void checkAsyncExecutedBlockCaptures(const BlockDataRegion &B,
58 const Expr *RetE) const;
59 bool isSemaphoreCaptured(const BlockDecl &B) const;
65};
66}
67
68SourceRange StackAddrEscapeChecker::genName(raw_ostream &os, const MemRegion *R,
70
74 os << "Address of ";
75
76
77 if (const auto *CR = dyn_cast(R)) {
79 os << "stack memory associated with a compound literal "
80 "declared on line "
83 } else if (const auto *AR = dyn_cast(R)) {
84 const Expr *ARE = AR->getExpr();
87 os << "stack memory allocated by call to alloca() on line "
88 << SM.getExpansionLineNumber(L);
89 } else if (const auto *BR = dyn_cast(R)) {
90 const BlockDecl *BD = BR->getCodeRegion()->getDecl();
93 os << "stack-allocated block declared on line "
94 << SM.getExpansionLineNumber(L);
95 } else if (const auto *VR = dyn_cast(R)) {
96 os << "stack memory associated with local variable '" << VR->getString()
97 << '\'';
98 range = VR->getDecl()->getSourceRange();
99 } else if (const auto *LER = dyn_cast(R)) {
100 QualType Ty = LER->getValueType().getLocalUnqualifiedType();
101 os << "stack memory associated with temporary object of type '";
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(R)) {
108 QualType Ty = TOR->getValueType().getLocalUnqualifiedType();
109 os << "stack memory associated with temporary object of type '";
111 os << "'";
112 range = TOR->getExpr()->getSourceRange();
113 } else {
114 llvm_unreachable("Invalid region in ReturnStackAddressChecker.");
115 }
116
118}
119
120bool StackAddrEscapeChecker::isNotInCurrentFrame(const MemRegion *R,
123 return S->getStackFrame() != C.getStackFrame();
124}
125
126bool StackAddrEscapeChecker::isSemaphoreCaptured(const BlockDecl &B) const {
127 if (!dispatch_semaphore_tII)
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)
132 return true;
133 }
134 return false;
135}
136
138StackAddrEscapeChecker::getCapturedStackRegions(const BlockDataRegion &B,
142 SVal Val = C.getState()->getSVal(Var.getCapturedRegion());
144 if (Region && isa(Region->getMemorySpace()))
145 Regions.push_back(Region);
146 }
147 return Regions;
148}
149
150void StackAddrEscapeChecker::EmitStackError(CheckerContext &C,
152 const Expr *RetE) const {
154 if (!N)
155 return;
156 if (!BT_returnstack)
157 BT_returnstack = std::make_unique(
158 CheckNames[CK_StackAddrEscapeChecker],
159 "Return of address to stack-allocated memory");
160
162 llvm::raw_svector_ostream os(buf);
164 os << " returned to caller";
165 auto report =
166 std::make_unique(*BT_returnstack, os.str(), N);
168 if (range.isValid())
169 report->addRange(range);
170 C.emitReport(std::move(report));
171}
172
173void StackAddrEscapeChecker::checkAsyncExecutedBlockCaptures(
175
176
177
178
179
180
181
182 if (isSemaphoreCaptured(*B.getDecl()))
183 return;
184 for (const MemRegion *Region : getCapturedStackRegions(B, C)) {
185
186
187
188
189
190
191 if (isa(Region))
192 continue;
194 if (!N)
195 continue;
196 if (!BT_capturedstackasync)
197 BT_capturedstackasync = std::make_unique(
198 CheckNames[CK_StackAddrAsyncEscapeChecker],
199 "Address of stack-allocated memory is captured");
201 llvm::raw_svector_ostream Out(Buf);
203 Out << " is captured by an asynchronously-executed block";
204 auto Report = std::make_unique(
205 *BT_capturedstackasync, Out.str(), N);
206 if (Range.isValid())
208 C.emitReport(std::move(Report));
209 }
210}
211
212void StackAddrEscapeChecker::checkReturnedBlockCaptures(
214 for (const MemRegion *Region : getCapturedStackRegions(B, C)) {
215 if (isNotInCurrentFrame(Region, C))
216 continue;
218 if (!N)
219 continue;
220 if (!BT_capturedstackret)
221 BT_capturedstackret = std::make_unique(
222 CheckNames[CK_StackAddrEscapeChecker],
223 "Address of stack-allocated memory is captured");
225 llvm::raw_svector_ostream Out(Buf);
227 Out << " is captured by a returned block";
228 auto Report = std::make_unique(*BT_capturedstackret,
229 Out.str(), N);
230 if (Range.isValid())
232 C.emitReport(std::move(Report));
233 }
234}
235
236void StackAddrEscapeChecker::checkPreCall(const CallEvent &Call,
238 if (!ChecksEnabled[CK_StackAddrAsyncEscapeChecker])
239 return;
240 if (.isGlobalCFunction("dispatch_after") &&
241 .isGlobalCFunction("dispatch_async"))
242 return;
243 for (unsigned Idx = 0, NumArgs = Call.getNumArgs(); Idx < NumArgs; ++Idx) {
244 if (const BlockDataRegion *B = dyn_cast_or_null(
245 Call.getArgSVal(Idx).getAsRegion()))
246 checkAsyncExecutedBlockCaptures(*B, C);
247 }
248}
249
250void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS,
252 if (!ChecksEnabled[CK_StackAddrEscapeChecker])
253 return;
254
256 if (!RetE)
257 return;
259
261 const MemRegion *R = V.getAsRegion();
262 if (!R)
263 return;
264
265 if (const BlockDataRegion *B = dyn_cast(R))
266 checkReturnedBlockCaptures(*B, C);
267
268 if (!isa(R->getMemorySpace()) || isNotInCurrentFrame(R, C))
269 return;
270
271
272
273
274 if (const ExprWithCleanups *Cleanup = dyn_cast(RetE))
275 RetE = Cleanup->getSubExpr();
277 return;
278
279
280
281 if (const auto *ICE = dyn_cast(RetE)) {
282 if (isa(R) &&
283 ICE->getCastKind() == CK_CopyAndAutoreleaseBlockObject) {
284 return;
285 }
286 }
287
288 EmitStackError(C, R, RetE);
289}
290
292 assert(R);
295 return SSR;
297 return GSR;
298 }
299
300
302 if (const auto *OriginReg = SymReg->getSymbol()->getOriginRegion())
304 }
305 return nullptr;
306}
307
310 while (const auto *SymReg = dyn_cast(Reg)) {
311 const auto *OriginReg = SymReg->getSymbol()->getOriginRegion();
312 if (!OriginReg)
313 break;
315 }
316 return Reg;
317}
318
320 assert(Referrer);
321 const StringRef ReferrerMemorySpace = [](const MemSpaceRegion *Space) {
322 if (isa(Space))
323 return "static";
324 if (isa(Space))
325 return "global";
326 assert(isa(Space));
327
328 return "caller";
330
332 if (const auto *SymReg = dyn_cast(Referrer);
333 SymReg && SymReg->getSymbol()->getOriginRegion()) {
334 Referrer = SymReg->getSymbol()->getOriginRegion()->getBaseRegion();
335 } else if (isa(Referrer)) {
336
337
338
339 return std::nullopt;
340 } else if (isa(Referrer)) {
341
342
343 return std::nullopt;
344 } else {
345 assert(false && "Unexpected referrer region type.");
346 return std::nullopt;
347 }
348 }
349 assert(Referrer);
351
352 std::string buf;
353 llvm::raw_string_ostream os(buf);
354 os << ReferrerMemorySpace << " variable ";
356 return buf;
357}
358
359
360
363 if (!SymReg)
364 return false;
365 SymbolRef Symbol = SymReg->getSymbol();
366
367 const auto *DerS = dyn_cast(Symbol);
368 return DerS && isa_and_nonnull(DerS->getParentSymbol());
369}
370
371void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS,
373 if (!ChecksEnabled[CK_StackAddrEscapeChecker])
374 return;
375
377
378 bool ExitingTopFrame =
379 Ctx.getPredecessor()->getLocationContext()->inTopFrame();
380
381 if (ExitingTopFrame &&
383 Node->getFirstPred()) {
384
385
386
387
389 }
390
391
392
394 private:
397 const bool TopFrame;
398
399
400
401
402 bool checkForDanglingStackVariable(const MemRegion *Referrer,
405 const auto *ReferredMemSpace =
407
408 if (!ReferrerMemSpace || !ReferredMemSpace)
409 return false;
410
411 const auto *ReferrerStackSpace =
413
414 if (!ReferrerStackSpace)
415 return false;
416
417 if (const auto *ReferredFrame = ReferredMemSpace->getStackFrame();
418 ReferredFrame != PoppedFrame) {
419 return false;
420 }
421
422 if (ReferrerStackSpace->getStackFrame()->isParentOf(PoppedFrame)) {
423 V.emplace_back(Referrer, Referred);
424 return true;
425 }
426 if (isa(ReferrerMemSpace) &&
427
428 isa(Referrer->getBaseRegion()) &&
429 ReferrerStackSpace->getStackFrame() == PoppedFrame && TopFrame) {
430
431 V.emplace_back(Referrer, Referred);
432 return true;
433 }
434 return false;
435 }
436
437
438
439
440
441 void recordInInvalidatedRegions(const MemRegion *Region) {
444 }
445
446 public:
448
449
450
451
453
455 : Ctx(CC), PoppedFrame(CC.getStackFrame()), TopFrame(TopFrame) {}
456
458 SVal Val) override {
459 recordInInvalidatedRegions(Region);
461 if (!VR)
462 return true;
463
464 if (checkForDanglingStackVariable(Region, VR))
465 return true;
466
467
468 if (!isa_and_nonnull(
470 return true;
471 if (VR && VR->hasStackStorage() && !isNotInCurrentFrame(VR, Ctx))
472 V.emplace_back(Region, VR);
473 return true;
474 }
475 };
476
477 CallBack Cb(Ctx, ExitingTopFrame);
479 State->getStateManager().getStoreManager().iterBindings(State->getStore(),
480 Cb);
481
482 if (Cb.V.empty())
483 return;
484
485
486 ExplodedNode *N = Ctx.generateNonFatalErrorNode(State, Node);
487 if (!N)
488 return;
489
490 if (!BT_stackleak)
491 BT_stackleak =
492 std::make_unique(CheckNames[CK_StackAddrEscapeChecker],
493 "Stack address leaks outside of stack frame");
494
495 for (const auto &P : Cb.V) {
496 const MemRegion *Referrer = P.first->getBaseRegion();
497 const MemRegion *Referred = P.second;
499 continue;
500 }
501
502
503 const StringRef CommonSuffix =
504 " upon returning to the caller. This will be a dangling reference";
506 llvm::raw_svector_ostream Out(Buf);
507 const SourceRange Range = genName(Out, Referred, Ctx.getASTContext());
508
509 if (isa<CXXTempObjectRegion, CXXLifetimeExtendedObjectRegion>(Referrer)) {
510 Out << " is still referred to by a temporary object on the stack"
511 << CommonSuffix;
513 std::make_unique(*BT_stackleak, Out.str(), N);
514 if (Range.isValid())
516 Ctx.emitReport(std::move(Report));
517 return;
518 }
519
521 if (!ReferrerVariable) {
522 continue;
523 }
524
525 Out << " is still referred to by the " << *ReferrerVariable << CommonSuffix;
527 std::make_unique(*BT_stackleak, Out.str(), N);
528 if (Range.isValid())
530
531 Ctx.emitReport(std::move(Report));
532 }
533}
534
535void ento::registerStackAddrEscapeBase(CheckerManager &mgr) {
537}
538
539bool ento::shouldRegisterStackAddrEscapeBase(const CheckerManager &mgr) {
540 return true;
541}
542
543#define REGISTER_CHECKER(name) \
544 void ento::register##name(CheckerManager &Mgr) { \
545 StackAddrEscapeChecker *Chk = Mgr.getChecker(); \
546 Chk->ChecksEnabled[StackAddrEscapeChecker::CK_##name] = true; \
547 Chk->CheckNames[StackAddrEscapeChecker::CK_##name] = \
548 Mgr.getCurrentCheckerName(); \
549 } \
550 \
551 bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; }
552
Defines the clang::Expr interface and subclasses for C++ expressions.
Defines the SourceManager interface.
#define REGISTER_CHECKER(name)
static std::optional< std::string > printReferrer(const MemRegion *Referrer)
static bool isInvalidatedSymbolRegion(const MemRegion *Region)
Check whether Region refers to a freshly minted symbol after an opaque function call.
static const MemSpaceRegion * getStackOrGlobalSpaceRegion(const MemRegion *R)
static const MemRegion * getOriginBaseRegion(const MemRegion *Reg)
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
SourceManager & getSourceManager()
const clang::PrintingPolicy & getPrintingPolicy() const
Represents a block literal declaration, which is like an unnamed FunctionDecl.
ArrayRef< Capture > captures() const
SourceRange getSourceRange() const override LLVM_READONLY
Source range that this declaration covers.
CompoundLiteralExpr - [C99 6.5.2.5].
SourceLocation getBeginLoc() const LLVM_READONLY
ASTContext & getASTContext() const LLVM_READONLY
SourceLocation getBeginLoc() const LLVM_READONLY
Represents an expression – generally a full-expression – that introduces cleanups to be run at the en...
This represents one expression.
Expr * IgnoreParens() LLVM_READONLY
Skip past any parentheses which might surround this expression until reaching a fixed point.
One of these records is kept for each identifier that is lexed.
IdentifierInfo & get(StringRef Name)
Return the identifier token info for the specified named identifier.
A (possibly-)qualified type.
void print(raw_ostream &OS, const PrintingPolicy &Policy, const Twine &PlaceHolder=Twine(), unsigned Indentation=0) const
ReturnStmt - This represents a return, optionally of an expression: return; return 4;.
Encodes a location in the source.
This class handles loading and caching of source files into memory.
A trivial tuple used to represent a source range.
It represents a stack frame of the call stack (based on CallEvent).
SourceRange getSourceRange() const LLVM_READONLY
SourceLocation tokens are not useful in isolation - they are low level value objects created/interpre...
SourceLocation getBeginLoc() const LLVM_READONLY
bool isRecordType() const
BlockDataRegion - A region that represents a block instance.
LLVM_ATTRIBUTE_RETURNS_NONNULL const BlockDecl * getDecl() const
llvm::iterator_range< referenced_vars_iterator > referenced_vars() const
Represents an abstract call to a function or method along a particular path.
CHECKER * registerChecker(AT &&... Args)
Used to register checkers.
This wrapper is used to ensure that only StringRefs originating from the CheckerRegistry are used as ...
static const ProgramPointTag * cleanupNodeTag()
A tag to track convenience transitions, which can be removed at cleanup.
MemRegion - The root abstract class for all memory regions.
LLVM_ATTRIBUTE_RETURNS_NONNULL const MemSpaceRegion * getMemorySpace() const
bool hasStackStorage() const
LLVM_ATTRIBUTE_RETURNS_NONNULL const MemRegion * getBaseRegion() const
virtual void printPretty(raw_ostream &os) const
Print the region for use in diagnostics.
const RegionTy * getAs() const
virtual bool canPrintPretty() const
Returns true if this region can be printed in a user-friendly way.
MemSpaceRegion - A memory region that represents a "memory space"; for example, the set of global var...
A Range represents the closed range [from, to].
SVal - This represents a symbolic expression, which can be either an L-value or an R-value.
const MemRegion * getAsRegion() const
virtual bool HandleBinding(StoreManager &SMgr, Store store, const MemRegion *region, SVal val)=0
SymbolicRegion - A special, "non-concrete" region.
const void * Store
Store - This opaque type encapsulates an immutable mapping from locations to values.
RangeSelector range(RangeSelector Begin, RangeSelector End)
DEPRECATED. Use enclose.
The JSON file list parser is used to communicate input to InstallAPI.
const FunctionProtoType * T