1 | //=== StackAddrEscapeChecker.cpp ----------------------------------*- C++ -*--// |
2 | // |
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 |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
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. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
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; |
26 | using namespace ento; |
27 | |
28 | namespace { |
29 | class StackAddrEscapeChecker |
30 | : public Checker<check::PreCall, check::PreStmt<ReturnStmt>, |
31 | check::EndFunction> { |
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; |
37 | |
38 | public: |
39 | enum CheckKind { |
40 | CK_StackAddrEscapeChecker, |
41 | CK_StackAddrAsyncEscapeChecker, |
42 | CK_NumCheckKinds |
43 | }; |
44 | |
45 | bool ChecksEnabled[CK_NumCheckKinds] = {false}; |
46 | CheckerNameRef CheckNames[CK_NumCheckKinds]; |
47 | |
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; |
51 | |
52 | private: |
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, |
61 | ASTContext &Ctx); |
62 | static SmallVector<const MemRegion *, 4> |
63 | getCapturedStackRegions(const BlockDataRegion &B, CheckerContext &C); |
64 | static bool isNotInCurrentFrame(const MemRegion *R, CheckerContext &C); |
65 | }; |
66 | } // namespace |
67 | |
68 | SourceRange StackAddrEscapeChecker::genName(raw_ostream &os, const MemRegion *R, |
69 | ASTContext &Ctx) { |
70 | // Get the base region, stripping away fields and elements. |
71 | R = R->getBaseRegion(); |
72 | SourceManager &SM = Ctx.getSourceManager(); |
73 | SourceRange range; |
74 | os << "Address of " ; |
75 | |
76 | // Check if the region is a compound literal. |
77 | if (const auto *CR = dyn_cast<CompoundLiteralRegion>(Val: R)) { |
78 | const CompoundLiteralExpr *CL = CR->getLiteralExpr(); |
79 | os << "stack memory associated with a compound literal " |
80 | "declared on line " |
81 | << SM.getExpansionLineNumber(Loc: CL->getBeginLoc()) << " returned to caller" ; |
82 | range = CL->getSourceRange(); |
83 | } else if (const auto *AR = dyn_cast<AllocaRegion>(Val: 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(Loc: L); |
89 | } else if (const auto *BR = dyn_cast<BlockDataRegion>(Val: 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(Loc: L); |
95 | } else if (const auto *VR = dyn_cast<VarRegion>(Val: 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<CXXLifetimeExtendedObjectRegion>(Val: R)) { |
100 | QualType Ty = LER->getValueType().getLocalUnqualifiedType(); |
101 | os << "stack memory associated with temporary object of type '" ; |
102 | Ty.print(OS&: os, Policy: 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>(Val: R)) { |
108 | QualType Ty = TOR->getValueType().getLocalUnqualifiedType(); |
109 | os << "stack memory associated with temporary object of type '" ; |
110 | Ty.print(OS&: os, Policy: Ctx.getPrintingPolicy()); |
111 | os << "'" ; |
112 | range = TOR->getExpr()->getSourceRange(); |
113 | } else { |
114 | llvm_unreachable("Invalid region in ReturnStackAddressChecker." ); |
115 | } |
116 | |
117 | return range; |
118 | } |
119 | |
120 | bool StackAddrEscapeChecker::isNotInCurrentFrame(const MemRegion *R, |
121 | CheckerContext &C) { |
122 | const StackSpaceRegion *S = cast<StackSpaceRegion>(Val: R->getMemorySpace()); |
123 | return S->getStackFrame() != C.getStackFrame(); |
124 | } |
125 | |
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) |
132 | return true; |
133 | } |
134 | return false; |
135 | } |
136 | |
137 | SmallVector<const MemRegion *, 4> |
138 | StackAddrEscapeChecker::getCapturedStackRegions(const BlockDataRegion &B, |
139 | CheckerContext &C) { |
140 | SmallVector<const MemRegion *, 4> Regions; |
141 | for (auto Var : B.referenced_vars()) { |
142 | SVal Val = C.getState()->getSVal(R: Var.getCapturedRegion()); |
143 | const MemRegion *Region = Val.getAsRegion(); |
144 | if (Region && isa<StackSpaceRegion>(Val: Region->getMemorySpace())) |
145 | Regions.push_back(Elt: Region); |
146 | } |
147 | return Regions; |
148 | } |
149 | |
150 | void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, |
151 | const MemRegion *R, |
152 | const Expr *RetE) const { |
153 | ExplodedNode *N = C.generateNonFatalErrorNode(); |
154 | if (!N) |
155 | return; |
156 | if (!BT_returnstack) |
157 | BT_returnstack = std::make_unique<BugType>( |
158 | args: CheckNames[CK_StackAddrEscapeChecker], |
159 | args: "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, Ctx&: C.getASTContext()); |
164 | os << " returned to caller" ; |
165 | auto report = |
166 | std::make_unique<PathSensitiveBugReport>(args&: *BT_returnstack, args: os.str(), args&: N); |
167 | report->addRange(R: RetE->getSourceRange()); |
168 | if (range.isValid()) |
169 | report->addRange(R: range); |
170 | C.emitReport(R: std::move(report)); |
171 | } |
172 | |
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: *B.getDecl())) |
183 | return; |
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>(Val: Region)) |
192 | continue; |
193 | ExplodedNode *N = C.generateNonFatalErrorNode(); |
194 | if (!N) |
195 | continue; |
196 | if (!BT_capturedstackasync) |
197 | BT_capturedstackasync = std::make_unique<BugType>( |
198 | args: CheckNames[CK_StackAddrAsyncEscapeChecker], |
199 | args: "Address of stack-allocated memory is captured" ); |
200 | SmallString<128> Buf; |
201 | llvm::raw_svector_ostream Out(Buf); |
202 | SourceRange Range = genName(os&: Out, R: Region, Ctx&: C.getASTContext()); |
203 | Out << " is captured by an asynchronously-executed block" ; |
204 | auto Report = std::make_unique<PathSensitiveBugReport>( |
205 | args&: *BT_capturedstackasync, args: Out.str(), args&: N); |
206 | if (Range.isValid()) |
207 | Report->addRange(R: Range); |
208 | C.emitReport(R: std::move(Report)); |
209 | } |
210 | } |
211 | |
212 | void StackAddrEscapeChecker::checkReturnedBlockCaptures( |
213 | const BlockDataRegion &B, CheckerContext &C) const { |
214 | for (const MemRegion *Region : getCapturedStackRegions(B, C)) { |
215 | if (isNotInCurrentFrame(R: Region, C)) |
216 | continue; |
217 | ExplodedNode *N = C.generateNonFatalErrorNode(); |
218 | if (!N) |
219 | continue; |
220 | if (!BT_capturedstackret) |
221 | BT_capturedstackret = std::make_unique<BugType>( |
222 | args: CheckNames[CK_StackAddrEscapeChecker], |
223 | args: "Address of stack-allocated memory is captured" ); |
224 | SmallString<128> Buf; |
225 | llvm::raw_svector_ostream Out(Buf); |
226 | SourceRange Range = genName(os&: Out, R: Region, Ctx&: C.getASTContext()); |
227 | Out << " is captured by a returned block" ; |
228 | auto Report = std::make_unique<PathSensitiveBugReport>(args&: *BT_capturedstackret, |
229 | args: Out.str(), args&: N); |
230 | if (Range.isValid()) |
231 | Report->addRange(R: Range); |
232 | C.emitReport(R: std::move(Report)); |
233 | } |
234 | } |
235 | |
236 | void StackAddrEscapeChecker::checkPreCall(const CallEvent &Call, |
237 | CheckerContext &C) const { |
238 | if (!ChecksEnabled[CK_StackAddrAsyncEscapeChecker]) |
239 | return; |
240 | if (!Call.isGlobalCFunction(SpecificName: "dispatch_after" ) && |
241 | !Call.isGlobalCFunction(SpecificName: "dispatch_async" )) |
242 | return; |
243 | for (unsigned Idx = 0, NumArgs = Call.getNumArgs(); Idx < NumArgs; ++Idx) { |
244 | if (const BlockDataRegion *B = dyn_cast_or_null<BlockDataRegion>( |
245 | Val: Call.getArgSVal(Index: Idx).getAsRegion())) |
246 | checkAsyncExecutedBlockCaptures(B: *B, C); |
247 | } |
248 | } |
249 | |
250 | void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, |
251 | CheckerContext &C) const { |
252 | if (!ChecksEnabled[CK_StackAddrEscapeChecker]) |
253 | return; |
254 | |
255 | const Expr *RetE = RS->getRetValue(); |
256 | if (!RetE) |
257 | return; |
258 | RetE = RetE->IgnoreParens(); |
259 | |
260 | SVal V = C.getSVal(RetE); |
261 | const MemRegion *R = V.getAsRegion(); |
262 | if (!R) |
263 | return; |
264 | |
265 | if (const BlockDataRegion *B = dyn_cast<BlockDataRegion>(Val: R)) |
266 | checkReturnedBlockCaptures(B: *B, C); |
267 | |
268 | if (!isa<StackSpaceRegion>(Val: R->getMemorySpace()) || isNotInCurrentFrame(R, C)) |
269 | return; |
270 | |
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>(Val: RetE)) |
275 | RetE = Cleanup->getSubExpr(); |
276 | if (isa<CXXConstructExpr>(Val: RetE) && RetE->getType()->isRecordType()) |
277 | return; |
278 | |
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>(Val: RetE)) { |
282 | if (isa<BlockDataRegion>(Val: R) && |
283 | ICE->getCastKind() == CK_CopyAndAutoreleaseBlockObject) { |
284 | return; |
285 | } |
286 | } |
287 | |
288 | EmitStackError(C, R, RetE); |
289 | } |
290 | |
291 | void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, |
292 | CheckerContext &Ctx) const { |
293 | if (!ChecksEnabled[CK_StackAddrEscapeChecker]) |
294 | return; |
295 | |
296 | ProgramStateRef State = Ctx.getState(); |
297 | |
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 { |
301 | private: |
302 | CheckerContext &Ctx; |
303 | const StackFrameContext *PoppedFrame; |
304 | |
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>(); |
314 | |
315 | if (!ReferrerMemSpace || !ReferredMemSpace) |
316 | return false; |
317 | |
318 | const auto *ReferrerFrame = ReferrerMemSpace->getStackFrame(); |
319 | const auto *ReferredFrame = ReferredMemSpace->getStackFrame(); |
320 | |
321 | if (ReferrerMemSpace && ReferredMemSpace) { |
322 | if (ReferredFrame == PoppedFrame && |
323 | ReferrerFrame->isParentOf(LC: PoppedFrame)) { |
324 | V.emplace_back(Args&: Referrer, Args&: Referred); |
325 | return true; |
326 | } |
327 | } |
328 | return false; |
329 | } |
330 | |
331 | public: |
332 | SmallVector<std::pair<const MemRegion *, const MemRegion *>, 10> V; |
333 | |
334 | CallBack(CheckerContext &CC) : Ctx(CC), PoppedFrame(CC.getStackFrame()) {} |
335 | |
336 | bool HandleBinding(StoreManager &SMgr, Store S, const MemRegion *Region, |
337 | SVal Val) override { |
338 | const MemRegion *VR = Val.getAsRegion(); |
339 | if (!VR) |
340 | return true; |
341 | |
342 | if (checkForDanglingStackVariable(Referrer: Region, Referred: VR)) |
343 | return true; |
344 | |
345 | // Check the globals for the same. |
346 | if (!isa<GlobalsSpaceRegion>(Val: Region->getMemorySpace())) |
347 | return true; |
348 | if (VR && VR->hasStackStorage() && !isNotInCurrentFrame(R: VR, C&: Ctx)) |
349 | V.emplace_back(Args&: Region, Args&: VR); |
350 | return true; |
351 | } |
352 | }; |
353 | |
354 | CallBack Cb(Ctx); |
355 | State->getStateManager().getStoreManager().iterBindings(store: State->getStore(), |
356 | f&: Cb); |
357 | |
358 | if (Cb.V.empty()) |
359 | return; |
360 | |
361 | // Generate an error node. |
362 | ExplodedNode *N = Ctx.generateNonFatalErrorNode(State); |
363 | if (!N) |
364 | return; |
365 | |
366 | if (!BT_stackleak) |
367 | BT_stackleak = |
368 | std::make_unique<BugType>(args: CheckNames[CK_StackAddrEscapeChecker], |
369 | args: "Stack address stored into global variable" ); |
370 | |
371 | for (const auto &P : Cb.V) { |
372 | const MemRegion *Referrer = P.first->getBaseRegion(); |
373 | const MemRegion *Referred = P.second; |
374 | |
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(os&: Out, R: Referred, Ctx&: Ctx.getASTContext()); |
381 | |
382 | if (isa<CXXTempObjectRegion, CXXLifetimeExtendedObjectRegion>(Val: Referrer)) { |
383 | Out << " is still referred to by a temporary object on the stack " |
384 | << CommonSuffix; |
385 | auto Report = |
386 | std::make_unique<PathSensitiveBugReport>(args&: *BT_stackleak, args: Out.str(), args&: N); |
387 | if (Range.isValid()) |
388 | Report->addRange(R: Range); |
389 | Ctx.emitReport(R: std::move(Report)); |
390 | return; |
391 | } |
392 | |
393 | const StringRef ReferrerMemorySpace = [](const MemSpaceRegion *Space) { |
394 | if (isa<StaticGlobalSpaceRegion>(Val: Space)) |
395 | return "static" ; |
396 | if (isa<GlobalsSpaceRegion>(Val: Space)) |
397 | return "global" ; |
398 | assert(isa<StackSpaceRegion>(Space)); |
399 | return "stack" ; |
400 | }(Referrer->getMemorySpace()); |
401 | |
402 | // We should really only have VarRegions here. |
403 | // Anything else is really surprising, and we should get notified if such |
404 | // ever happens. |
405 | const auto *ReferrerVar = dyn_cast<VarRegion>(Val: Referrer); |
406 | if (!ReferrerVar) { |
407 | assert(false && "We should have a VarRegion here" ); |
408 | continue; // Defensively skip this one. |
409 | } |
410 | const std::string ReferrerVarName = |
411 | ReferrerVar->getDecl()->getDeclName().getAsString(); |
412 | |
413 | Out << " is still referred to by the " << ReferrerMemorySpace |
414 | << " variable '" << ReferrerVarName << "' " << CommonSuffix; |
415 | auto Report = |
416 | std::make_unique<PathSensitiveBugReport>(args&: *BT_stackleak, args: Out.str(), args&: N); |
417 | if (Range.isValid()) |
418 | Report->addRange(R: Range); |
419 | |
420 | Ctx.emitReport(R: std::move(Report)); |
421 | } |
422 | } |
423 | |
424 | void ento::registerStackAddrEscapeBase(CheckerManager &mgr) { |
425 | mgr.registerChecker<StackAddrEscapeChecker>(); |
426 | } |
427 | |
428 | bool ento::shouldRegisterStackAddrEscapeBase(const CheckerManager &mgr) { |
429 | return true; |
430 | } |
431 | |
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(); \ |
438 | } \ |
439 | \ |
440 | bool ento::shouldRegister##name(const CheckerManager &mgr) { return true; } |
441 | |
442 | REGISTER_CHECKER(StackAddrEscapeChecker) |
443 | REGISTER_CHECKER(StackAddrAsyncEscapeChecker) |
444 | |