1 //== ValistChecker.cpp - stdarg.h macro usage checker -----------*- 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 defines checkers which detect usage of uninitialized va_list values
10 // and va_start calls with no matching va_end.
11 //
12 //===----------------------------------------------------------------------===//
13
14 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
15 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
16 #include "clang/StaticAnalyzer/Core/Checker.h"
17 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
18 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
19 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
20 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
21
22 using namespace clang;
23 using namespace ento;
24
25 REGISTER_SET_WITH_PROGRAMSTATE(InitializedVALists, const MemRegion *)
26
27 namespace {
28 typedef SmallVector<const MemRegion *, 2> RegionVector;
29
30 class ValistChecker : public Checker<check::PreCall, check::PreStmt<VAArgExpr>,
31 check::DeadSymbols> {
32 mutable std::unique_ptr<BugType> BT_leakedvalist, BT_uninitaccess;
33
34 struct VAListAccepter {
35 CallDescription Func;
36 int VAListPos;
37 };
38 static const SmallVector<VAListAccepter, 15> VAListAccepters;
39 static const CallDescription VaStart, VaEnd, VaCopy;
40
41 public:
42 enum CheckKind {
43 CK_Uninitialized,
44 CK_Unterminated,
45 CK_CopyToSelf,
46 CK_NumCheckKinds
47 };
48
49 bool ChecksEnabled[CK_NumCheckKinds] = {false};
50 CheckerNameRef CheckNames[CK_NumCheckKinds];
51
52 void checkPreStmt(const VAArgExpr *VAA, CheckerContext &C) const;
53 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
54 void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
55
56 private:
57 const MemRegion *getVAListAsRegion(SVal SV, const Expr *VAExpr,
58 bool &IsSymbolic, CheckerContext &C) const;
59 const ExplodedNode *getStartCallSite(const ExplodedNode *N,
60 const MemRegion *Reg) const;
61
62 void reportUninitializedAccess(const MemRegion *VAList, StringRef Msg,
63 CheckerContext &C) const;
64 void reportLeakedVALists(const RegionVector &LeakedVALists, StringRef Msg1,
65 StringRef Msg2, CheckerContext &C, ExplodedNode *N,
66 bool ReportUninit = false) const;
67
68 void checkVAListStartCall(const CallEvent &Call, CheckerContext &C,
69 bool IsCopy) const;
70 void checkVAListEndCall(const CallEvent &Call, CheckerContext &C) const;
71
72 class ValistBugVisitor : public BugReporterVisitor {
73 public:
ValistBugVisitor(const MemRegion * Reg,bool IsLeak=false)74 ValistBugVisitor(const MemRegion *Reg, bool IsLeak = false)
75 : Reg(Reg), IsLeak(IsLeak) {}
Profile(llvm::FoldingSetNodeID & ID) const76 void Profile(llvm::FoldingSetNodeID &ID) const override {
77 static int X = 0;
78 ID.AddPointer(&X);
79 ID.AddPointer(Reg);
80 }
getEndPath(BugReporterContext & BRC,const ExplodedNode * EndPathNode,PathSensitiveBugReport & BR)81 PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC,
82 const ExplodedNode *EndPathNode,
83 PathSensitiveBugReport &BR) override {
84 if (!IsLeak)
85 return nullptr;
86
87 PathDiagnosticLocation L = BR.getLocation();
88 // Do not add the statement itself as a range in case of leak.
89 return std::make_shared<PathDiagnosticEventPiece>(L, BR.getDescription(),
90 false);
91 }
92 PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
93 BugReporterContext &BRC,
94 PathSensitiveBugReport &BR) override;
95
96 private:
97 const MemRegion *Reg;
98 bool IsLeak;
99 };
100 };
101
102 const SmallVector<ValistChecker::VAListAccepter, 15>
103 ValistChecker::VAListAccepters = {
104 {{"vfprintf", 3}, 2},
105 {{"vfscanf", 3}, 2},
106 {{"vprintf", 2}, 1},
107 {{"vscanf", 2}, 1},
108 {{"vsnprintf", 4}, 3},
109 {{"vsprintf", 3}, 2},
110 {{"vsscanf", 3}, 2},
111 {{"vfwprintf", 3}, 2},
112 {{"vfwscanf", 3}, 2},
113 {{"vwprintf", 2}, 1},
114 {{"vwscanf", 2}, 1},
115 {{"vswprintf", 4}, 3},
116 // vswprintf is the wide version of vsnprintf,
117 // vsprintf has no wide version
118 {{"vswscanf", 3}, 2}};
119
120 const CallDescription
121 ValistChecker::VaStart("__builtin_va_start", /*Args=*/2, /*Params=*/1),
122 ValistChecker::VaCopy("__builtin_va_copy", 2),
123 ValistChecker::VaEnd("__builtin_va_end", 1);
124 } // end anonymous namespace
125
checkPreCall(const CallEvent & Call,CheckerContext & C) const126 void ValistChecker::checkPreCall(const CallEvent &Call,
127 CheckerContext &C) const {
128 if (!Call.isGlobalCFunction())
129 return;
130 if (VaStart.matches(Call))
131 checkVAListStartCall(Call, C, false);
132 else if (VaCopy.matches(Call))
133 checkVAListStartCall(Call, C, true);
134 else if (VaEnd.matches(Call))
135 checkVAListEndCall(Call, C);
136 else {
137 for (auto FuncInfo : VAListAccepters) {
138 if (!FuncInfo.Func.matches(Call))
139 continue;
140 bool Symbolic;
141 const MemRegion *VAList =
142 getVAListAsRegion(Call.getArgSVal(FuncInfo.VAListPos),
143 Call.getArgExpr(FuncInfo.VAListPos), Symbolic, C);
144 if (!VAList)
145 return;
146
147 if (C.getState()->contains<InitializedVALists>(VAList))
148 return;
149
150 // We did not see va_start call, but the source of the region is unknown.
151 // Be conservative and assume the best.
152 if (Symbolic)
153 return;
154
155 SmallString<80> Errmsg("Function '");
156 Errmsg += FuncInfo.Func.getFunctionName();
157 Errmsg += "' is called with an uninitialized va_list argument";
158 reportUninitializedAccess(VAList, Errmsg.c_str(), C);
159 break;
160 }
161 }
162 }
163
getVAListAsRegion(SVal SV,const Expr * E,bool & IsSymbolic,CheckerContext & C) const164 const MemRegion *ValistChecker::getVAListAsRegion(SVal SV, const Expr *E,
165 bool &IsSymbolic,
166 CheckerContext &C) const {
167 const MemRegion *Reg = SV.getAsRegion();
168 if (!Reg)
169 return nullptr;
170 // TODO: In the future this should be abstracted away by the analyzer.
171 bool VaListModelledAsArray = false;
172 if (const auto *Cast = dyn_cast<CastExpr>(E)) {
173 QualType Ty = Cast->getType();
174 VaListModelledAsArray =
175 Ty->isPointerType() && Ty->getPointeeType()->isRecordType();
176 }
177 if (const auto *DeclReg = Reg->getAs<DeclRegion>()) {
178 if (isa<ParmVarDecl>(DeclReg->getDecl()))
179 Reg = C.getState()->getSVal(SV.castAs<Loc>()).getAsRegion();
180 }
181 IsSymbolic = Reg && Reg->getBaseRegion()->getAs<SymbolicRegion>();
182 // Some VarRegion based VA lists reach here as ElementRegions.
183 const auto *EReg = dyn_cast_or_null<ElementRegion>(Reg);
184 return (EReg && VaListModelledAsArray) ? EReg->getSuperRegion() : Reg;
185 }
186
checkPreStmt(const VAArgExpr * VAA,CheckerContext & C) const187 void ValistChecker::checkPreStmt(const VAArgExpr *VAA,
188 CheckerContext &C) const {
189 ProgramStateRef State = C.getState();
190 const Expr *VASubExpr = VAA->getSubExpr();
191 SVal VAListSVal = C.getSVal(VASubExpr);
192 bool Symbolic;
193 const MemRegion *VAList =
194 getVAListAsRegion(VAListSVal, VASubExpr, Symbolic, C);
195 if (!VAList)
196 return;
197 if (Symbolic)
198 return;
199 if (!State->contains<InitializedVALists>(VAList))
200 reportUninitializedAccess(
201 VAList, "va_arg() is called on an uninitialized va_list", C);
202 }
203
checkDeadSymbols(SymbolReaper & SR,CheckerContext & C) const204 void ValistChecker::checkDeadSymbols(SymbolReaper &SR,
205 CheckerContext &C) const {
206 ProgramStateRef State = C.getState();
207 InitializedVAListsTy TrackedVALists = State->get<InitializedVALists>();
208 RegionVector LeakedVALists;
209 for (auto Reg : TrackedVALists) {
210 if (SR.isLiveRegion(Reg))
211 continue;
212 LeakedVALists.push_back(Reg);
213 State = State->remove<InitializedVALists>(Reg);
214 }
215 if (ExplodedNode *N = C.addTransition(State))
216 reportLeakedVALists(LeakedVALists, "Initialized va_list", " is leaked", C,
217 N);
218 }
219
220 // This function traverses the exploded graph backwards and finds the node where
221 // the va_list is initialized. That node is used for uniquing the bug paths.
222 // It is not likely that there are several different va_lists that belongs to
223 // different stack frames, so that case is not yet handled.
224 const ExplodedNode *
getStartCallSite(const ExplodedNode * N,const MemRegion * Reg) const225 ValistChecker::getStartCallSite(const ExplodedNode *N,
226 const MemRegion *Reg) const {
227 const LocationContext *LeakContext = N->getLocationContext();
228 const ExplodedNode *StartCallNode = N;
229
230 bool FoundInitializedState = false;
231
232 while (N) {
233 ProgramStateRef State = N->getState();
234 if (!State->contains<InitializedVALists>(Reg)) {
235 if (FoundInitializedState)
236 break;
237 } else {
238 FoundInitializedState = true;
239 }
240 const LocationContext *NContext = N->getLocationContext();
241 if (NContext == LeakContext || NContext->isParentOf(LeakContext))
242 StartCallNode = N;
243 N = N->pred_empty() ? nullptr : *(N->pred_begin());
244 }
245
246 return StartCallNode;
247 }
248
reportUninitializedAccess(const MemRegion * VAList,StringRef Msg,CheckerContext & C) const249 void ValistChecker::reportUninitializedAccess(const MemRegion *VAList,
250 StringRef Msg,
251 CheckerContext &C) const {
252 if (!ChecksEnabled[CK_Uninitialized])
253 return;
254 if (ExplodedNode *N = C.generateErrorNode()) {
255 if (!BT_uninitaccess)
256 BT_uninitaccess.reset(new BugType(CheckNames[CK_Uninitialized],
257 "Uninitialized va_list",
258 categories::MemoryError));
259 auto R = std::make_unique<PathSensitiveBugReport>(*BT_uninitaccess, Msg, N);
260 R->markInteresting(VAList);
261 R->addVisitor(std::make_unique<ValistBugVisitor>(VAList));
262 C.emitReport(std::move(R));
263 }
264 }
265
reportLeakedVALists(const RegionVector & LeakedVALists,StringRef Msg1,StringRef Msg2,CheckerContext & C,ExplodedNode * N,bool ReportUninit) const266 void ValistChecker::reportLeakedVALists(const RegionVector &LeakedVALists,
267 StringRef Msg1, StringRef Msg2,
268 CheckerContext &C, ExplodedNode *N,
269 bool ReportUninit) const {
270 if (!(ChecksEnabled[CK_Unterminated] ||
271 (ChecksEnabled[CK_Uninitialized] && ReportUninit)))
272 return;
273 for (auto Reg : LeakedVALists) {
274 if (!BT_leakedvalist) {
275 // FIXME: maybe creating a new check name for this type of bug is a better
276 // solution.
277 BT_leakedvalist.reset(
278 new BugType(CheckNames[CK_Unterminated].getName().empty()
279 ? CheckNames[CK_Uninitialized]
280 : CheckNames[CK_Unterminated],
281 "Leaked va_list", categories::MemoryError,
282 /*SuppressOnSink=*/true));
283 }
284
285 const ExplodedNode *StartNode = getStartCallSite(N, Reg);
286 PathDiagnosticLocation LocUsedForUniqueing;
287
288 if (const Stmt *StartCallStmt = StartNode->getStmtForDiagnostics())
289 LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
290 StartCallStmt, C.getSourceManager(), StartNode->getLocationContext());
291
292 SmallString<100> Buf;
293 llvm::raw_svector_ostream OS(Buf);
294 OS << Msg1;
295 std::string VariableName = Reg->getDescriptiveName();
296 if (!VariableName.empty())
297 OS << " " << VariableName;
298 OS << Msg2;
299
300 auto R = std::make_unique<PathSensitiveBugReport>(
301 *BT_leakedvalist, OS.str(), N, LocUsedForUniqueing,
302 StartNode->getLocationContext()->getDecl());
303 R->markInteresting(Reg);
304 R->addVisitor(std::make_unique<ValistBugVisitor>(Reg, true));
305 C.emitReport(std::move(R));
306 }
307 }
308
checkVAListStartCall(const CallEvent & Call,CheckerContext & C,bool IsCopy) const309 void ValistChecker::checkVAListStartCall(const CallEvent &Call,
310 CheckerContext &C, bool IsCopy) const {
311 bool Symbolic;
312 const MemRegion *VAList =
313 getVAListAsRegion(Call.getArgSVal(0), Call.getArgExpr(0), Symbolic, C);
314 if (!VAList)
315 return;
316
317 ProgramStateRef State = C.getState();
318
319 if (IsCopy) {
320 const MemRegion *Arg2 =
321 getVAListAsRegion(Call.getArgSVal(1), Call.getArgExpr(1), Symbolic, C);
322 if (Arg2) {
323 if (ChecksEnabled[CK_CopyToSelf] && VAList == Arg2) {
324 RegionVector LeakedVALists{VAList};
325 if (ExplodedNode *N = C.addTransition(State))
326 reportLeakedVALists(LeakedVALists, "va_list",
327 " is copied onto itself", C, N, true);
328 return;
329 } else if (!State->contains<InitializedVALists>(Arg2) && !Symbolic) {
330 if (State->contains<InitializedVALists>(VAList)) {
331 State = State->remove<InitializedVALists>(VAList);
332 RegionVector LeakedVALists{VAList};
333 if (ExplodedNode *N = C.addTransition(State))
334 reportLeakedVALists(LeakedVALists, "Initialized va_list",
335 " is overwritten by an uninitialized one", C, N,
336 true);
337 } else {
338 reportUninitializedAccess(Arg2, "Uninitialized va_list is copied", C);
339 }
340 return;
341 }
342 }
343 }
344 if (State->contains<InitializedVALists>(VAList)) {
345 RegionVector LeakedVALists{VAList};
346 if (ExplodedNode *N = C.addTransition(State))
347 reportLeakedVALists(LeakedVALists, "Initialized va_list",
348 " is initialized again", C, N);
349 return;
350 }
351
352 State = State->add<InitializedVALists>(VAList);
353 C.addTransition(State);
354 }
355
checkVAListEndCall(const CallEvent & Call,CheckerContext & C) const356 void ValistChecker::checkVAListEndCall(const CallEvent &Call,
357 CheckerContext &C) const {
358 bool Symbolic;
359 const MemRegion *VAList =
360 getVAListAsRegion(Call.getArgSVal(0), Call.getArgExpr(0), Symbolic, C);
361 if (!VAList)
362 return;
363
364 // We did not see va_start call, but the source of the region is unknown.
365 // Be conservative and assume the best.
366 if (Symbolic)
367 return;
368
369 if (!C.getState()->contains<InitializedVALists>(VAList)) {
370 reportUninitializedAccess(
371 VAList, "va_end() is called on an uninitialized va_list", C);
372 return;
373 }
374 ProgramStateRef State = C.getState();
375 State = State->remove<InitializedVALists>(VAList);
376 C.addTransition(State);
377 }
378
VisitNode(const ExplodedNode * N,BugReporterContext & BRC,PathSensitiveBugReport &)379 PathDiagnosticPieceRef ValistChecker::ValistBugVisitor::VisitNode(
380 const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) {
381 ProgramStateRef State = N->getState();
382 ProgramStateRef StatePrev = N->getFirstPred()->getState();
383
384 const Stmt *S = N->getStmtForDiagnostics();
385 if (!S)
386 return nullptr;
387
388 StringRef Msg;
389 if (State->contains<InitializedVALists>(Reg) &&
390 !StatePrev->contains<InitializedVALists>(Reg))
391 Msg = "Initialized va_list";
392 else if (!State->contains<InitializedVALists>(Reg) &&
393 StatePrev->contains<InitializedVALists>(Reg))
394 Msg = "Ended va_list";
395
396 if (Msg.empty())
397 return nullptr;
398
399 PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
400 N->getLocationContext());
401 return std::make_shared<PathDiagnosticEventPiece>(Pos, Msg, true);
402 }
403
registerValistBase(CheckerManager & mgr)404 void ento::registerValistBase(CheckerManager &mgr) {
405 mgr.registerChecker<ValistChecker>();
406 }
407
shouldRegisterValistBase(const CheckerManager & mgr)408 bool ento::shouldRegisterValistBase(const CheckerManager &mgr) {
409 return true;
410 }
411
412 #define REGISTER_CHECKER(name) \
413 void ento::register##name##Checker(CheckerManager &mgr) { \
414 ValistChecker *checker = mgr.getChecker<ValistChecker>(); \
415 checker->ChecksEnabled[ValistChecker::CK_##name] = true; \
416 checker->CheckNames[ValistChecker::CK_##name] = \
417 mgr.getCurrentCheckerName(); \
418 } \
419 \
420 bool ento::shouldRegister##name##Checker(const CheckerManager &mgr) { \
421 return true; \
422 }
423
424 REGISTER_CHECKER(Uninitialized)
425 REGISTER_CHECKER(Unterminated)
426 REGISTER_CHECKER(CopyToSelf)
427