xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/cert/InvalidPtrChecker.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1 //== InvalidPtrChecker.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 InvalidPtrChecker which finds usages of possibly
10 // invalidated pointer.
11 // CERT SEI Rules ENV31-C and ENV34-C
12 // For more information see:
13 // https://wiki.sei.cmu.edu/confluence/x/8tYxBQ
14 // https://wiki.sei.cmu.edu/confluence/x/5NUxBQ
15 //===----------------------------------------------------------------------===//
16 
17 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19 #include "clang/StaticAnalyzer/Core/Checker.h"
20 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
21 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
22 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
23 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
24 
25 using namespace clang;
26 using namespace ento;
27 
28 namespace {
29 
30 class InvalidPtrChecker
31     : public Checker<check::Location, check::BeginFunction, check::PostCall> {
32 private:
33   // For accurate emission of NoteTags, the BugType of this checker should have
34   // a unique address.
35   BugType InvalidPtrBugType{this, "Use of invalidated pointer",
36                             categories::MemoryError};
37 
38   void EnvpInvalidatingCall(const CallEvent &Call, CheckerContext &C) const;
39 
40   using HandlerFn = void (InvalidPtrChecker::*)(const CallEvent &Call,
41                                                 CheckerContext &C) const;
42 
43   // SEI CERT ENV31-C
44 
45   // If set to true, consider getenv calls as invalidating operations on the
46   // environment variable buffer. This is implied in the standard, but in
47   // practice does not cause problems (in the commonly used environments).
48   bool InvalidatingGetEnv = false;
49 
50   // GetEnv can be treated invalidating and non-invalidating as well.
51   const CallDescription GetEnvCall{CDM::CLibrary, {"getenv"}, 1};
52 
53   const CallDescriptionMap<HandlerFn> EnvpInvalidatingFunctions = {
54       {{CDM::CLibrary, {"setenv"}, 3},
55        &InvalidPtrChecker::EnvpInvalidatingCall},
56       {{CDM::CLibrary, {"unsetenv"}, 1},
57        &InvalidPtrChecker::EnvpInvalidatingCall},
58       {{CDM::CLibrary, {"putenv"}, 1},
59        &InvalidPtrChecker::EnvpInvalidatingCall},
60       {{CDM::CLibrary, {"_putenv_s"}, 2},
61        &InvalidPtrChecker::EnvpInvalidatingCall},
62       {{CDM::CLibrary, {"_wputenv_s"}, 2},
63        &InvalidPtrChecker::EnvpInvalidatingCall},
64   };
65 
66   void postPreviousReturnInvalidatingCall(const CallEvent &Call,
67                                           CheckerContext &C) const;
68 
69   // SEI CERT ENV34-C
70   const CallDescriptionMap<HandlerFn> PreviousCallInvalidatingFunctions = {
71       {{CDM::CLibrary, {"setlocale"}, 2},
72        &InvalidPtrChecker::postPreviousReturnInvalidatingCall},
73       {{CDM::CLibrary, {"strerror"}, 1},
74        &InvalidPtrChecker::postPreviousReturnInvalidatingCall},
75       {{CDM::CLibrary, {"localeconv"}, 0},
76        &InvalidPtrChecker::postPreviousReturnInvalidatingCall},
77       {{CDM::CLibrary, {"asctime"}, 1},
78        &InvalidPtrChecker::postPreviousReturnInvalidatingCall},
79   };
80 
81   // The private members of this checker corresponding to commandline options
82   // are set in this function.
83   friend void ento::registerInvalidPtrChecker(CheckerManager &);
84 
85 public:
86   // Obtain the environment pointer from 'main()' (if present).
87   void checkBeginFunction(CheckerContext &C) const;
88 
89   // Handle functions in EnvpInvalidatingFunctions, that invalidate environment
90   // pointer from 'main()'
91   // Handle functions in PreviousCallInvalidatingFunctions.
92   // Also, check if invalidated region is passed to a
93   // conservatively evaluated function call as an argument.
94   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
95 
96   // Check if invalidated region is being dereferenced.
97   void checkLocation(SVal l, bool isLoad, const Stmt *S,
98                      CheckerContext &C) const;
99 
100 private:
101   const NoteTag *createEnvInvalidationNote(CheckerContext &C,
102                                            ProgramStateRef State,
103                                            StringRef FunctionName) const;
104 };
105 
106 } // namespace
107 
108 // Set of memory regions that were invalidated
REGISTER_SET_WITH_PROGRAMSTATE(InvalidMemoryRegions,const MemRegion *)109 REGISTER_SET_WITH_PROGRAMSTATE(InvalidMemoryRegions, const MemRegion *)
110 
111 // Stores the region of the environment pointer of 'main' (if present).
112 REGISTER_TRAIT_WITH_PROGRAMSTATE(MainEnvPtrRegion, const MemRegion *)
113 
114 // Stores the regions of environments returned by getenv calls.
115 REGISTER_SET_WITH_PROGRAMSTATE(GetenvEnvPtrRegions, const MemRegion *)
116 
117 // Stores key-value pairs, where key is function declaration and value is
118 // pointer to memory region returned by previous call of this function
119 REGISTER_MAP_WITH_PROGRAMSTATE(PreviousCallResultMap, const FunctionDecl *,
120                                const MemRegion *)
121 
122 const NoteTag *InvalidPtrChecker::createEnvInvalidationNote(
123     CheckerContext &C, ProgramStateRef State, StringRef FunctionName) const {
124 
125   const MemRegion *MainRegion = State->get<MainEnvPtrRegion>();
126   const auto GetenvRegions = State->get<GetenvEnvPtrRegions>();
127 
128   return C.getNoteTag([this, MainRegion, GetenvRegions,
129                        FunctionName = std::string{FunctionName}](
130                           PathSensitiveBugReport &BR, llvm::raw_ostream &Out) {
131     // Only handle the BugType of this checker.
132     if (&BR.getBugType() != &InvalidPtrBugType)
133       return;
134 
135     // Mark all regions that were interesting before as NOT interesting now
136     // to avoid extra notes coming from invalidation points higher up the
137     // bugpath. This ensures that only the last invalidation point is marked
138     // with a note tag.
139     llvm::SmallVector<std::string, 2> InvalidLocationNames;
140     if (BR.isInteresting(MainRegion)) {
141       BR.markNotInteresting(MainRegion);
142       InvalidLocationNames.push_back("the environment parameter of 'main'");
143     }
144     bool InterestingGetenvFound = false;
145     for (const MemRegion *MR : GetenvRegions) {
146       if (BR.isInteresting(MR)) {
147         BR.markNotInteresting(MR);
148         if (!InterestingGetenvFound) {
149           InterestingGetenvFound = true;
150           InvalidLocationNames.push_back(
151               "the environment returned by 'getenv'");
152         }
153       }
154     }
155 
156     // Emit note tag message.
157     if (InvalidLocationNames.size() >= 1)
158       Out << '\'' << FunctionName << "' call may invalidate "
159           << InvalidLocationNames[0];
160     if (InvalidLocationNames.size() == 2)
161       Out << ", and " << InvalidLocationNames[1];
162   });
163 }
164 
EnvpInvalidatingCall(const CallEvent & Call,CheckerContext & C) const165 void InvalidPtrChecker::EnvpInvalidatingCall(const CallEvent &Call,
166                                              CheckerContext &C) const {
167   // This callevent invalidates all previously generated pointers to the
168   // environment.
169   ProgramStateRef State = C.getState();
170   if (const MemRegion *MainEnvPtr = State->get<MainEnvPtrRegion>())
171     State = State->add<InvalidMemoryRegions>(MainEnvPtr);
172   for (const MemRegion *EnvPtr : State->get<GetenvEnvPtrRegions>())
173     State = State->add<InvalidMemoryRegions>(EnvPtr);
174 
175   StringRef FunctionName = Call.getCalleeIdentifier()->getName();
176   const NoteTag *InvalidationNote =
177       createEnvInvalidationNote(C, State, FunctionName);
178 
179   C.addTransition(State, InvalidationNote);
180 }
181 
postPreviousReturnInvalidatingCall(const CallEvent & Call,CheckerContext & C) const182 void InvalidPtrChecker::postPreviousReturnInvalidatingCall(
183     const CallEvent &Call, CheckerContext &C) const {
184   ProgramStateRef State = C.getState();
185 
186   const NoteTag *Note = nullptr;
187   const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
188   // Invalidate the region of the previously returned pointer - if there was
189   // one.
190   if (const MemRegion *const *Reg = State->get<PreviousCallResultMap>(FD)) {
191     const MemRegion *PrevReg = *Reg;
192     State = State->add<InvalidMemoryRegions>(PrevReg);
193     Note = C.getNoteTag([this, PrevReg, FD](PathSensitiveBugReport &BR,
194                                             llvm::raw_ostream &Out) {
195       if (!BR.isInteresting(PrevReg) || &BR.getBugType() != &InvalidPtrBugType)
196         return;
197       Out << '\'';
198       FD->getNameForDiagnostic(Out, FD->getASTContext().getLangOpts(), true);
199       Out << "' call may invalidate the result of the previous " << '\'';
200       FD->getNameForDiagnostic(Out, FD->getASTContext().getLangOpts(), true);
201       Out << '\'';
202     });
203   }
204 
205   const LocationContext *LCtx = C.getLocationContext();
206   const auto *CE = cast<CallExpr>(Call.getOriginExpr());
207 
208   // Function call will return a pointer to the new symbolic region.
209   DefinedOrUnknownSVal RetVal = C.getSValBuilder().conjureSymbolVal(
210       CE, LCtx, CE->getType(), C.blockCount());
211   State = State->BindExpr(CE, LCtx, RetVal);
212 
213   const auto *SymRegOfRetVal =
214       dyn_cast_or_null<SymbolicRegion>(RetVal.getAsRegion());
215   if (!SymRegOfRetVal)
216     return;
217 
218   // Remember to this region.
219   const MemRegion *MR = SymRegOfRetVal->getBaseRegion();
220   State = State->set<PreviousCallResultMap>(FD, MR);
221 
222   ExplodedNode *Node = C.addTransition(State, Note);
223   const NoteTag *PreviousCallNote = C.getNoteTag(
224       [this, MR](PathSensitiveBugReport &BR, llvm::raw_ostream &Out) {
225         if (!BR.isInteresting(MR) || &BR.getBugType() != &InvalidPtrBugType)
226           return;
227         Out << "previous function call was here";
228       });
229 
230   C.addTransition(State, Node, PreviousCallNote);
231 }
232 
233 // TODO: This seems really ugly. Simplify this.
findInvalidatedSymbolicBase(ProgramStateRef State,const MemRegion * Reg)234 static const MemRegion *findInvalidatedSymbolicBase(ProgramStateRef State,
235                                                     const MemRegion *Reg) {
236   while (Reg) {
237     if (State->contains<InvalidMemoryRegions>(Reg))
238       return Reg;
239     const auto *SymBase = Reg->getSymbolicBase();
240     if (!SymBase)
241       break;
242     const auto *SRV = dyn_cast<SymbolRegionValue>(SymBase->getSymbol());
243     if (!SRV)
244       break;
245     Reg = SRV->getRegion();
246     if (const auto *VarReg = dyn_cast<VarRegion>(SRV->getRegion()))
247       Reg = VarReg;
248   }
249   return nullptr;
250 }
251 
252 // Handle functions in EnvpInvalidatingFunctions, that invalidate environment
253 // pointer from 'main()' Also, check if invalidated region is passed to a
254 // function call as an argument.
checkPostCall(const CallEvent & Call,CheckerContext & C) const255 void InvalidPtrChecker::checkPostCall(const CallEvent &Call,
256                                       CheckerContext &C) const {
257 
258   ProgramStateRef State = C.getState();
259 
260   // Model 'getenv' calls
261   if (GetEnvCall.matches(Call)) {
262     const MemRegion *Region = Call.getReturnValue().getAsRegion();
263     if (Region) {
264       State = State->add<GetenvEnvPtrRegions>(Region);
265       C.addTransition(State);
266     }
267   }
268 
269   // Check if function invalidates 'envp' argument of 'main'
270   if (const auto *Handler = EnvpInvalidatingFunctions.lookup(Call))
271     (this->**Handler)(Call, C);
272 
273   // Check if function invalidates the result of previous call
274   if (const auto *Handler = PreviousCallInvalidatingFunctions.lookup(Call))
275     (this->**Handler)(Call, C);
276 
277   // If pedantic mode is on, regard 'getenv' calls invalidating as well
278   if (InvalidatingGetEnv && GetEnvCall.matches(Call))
279     postPreviousReturnInvalidatingCall(Call, C);
280 
281   // Check if one of the arguments of the function call is invalidated
282 
283   // If call was inlined, don't report invalidated argument
284   if (C.wasInlined)
285     return;
286 
287   for (unsigned I = 0, NumArgs = Call.getNumArgs(); I < NumArgs; ++I) {
288 
289     if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(
290             Call.getArgSVal(I).getAsRegion())) {
291       if (const MemRegion *InvalidatedSymbolicBase =
292               findInvalidatedSymbolicBase(State, SR)) {
293         ExplodedNode *ErrorNode = C.generateNonFatalErrorNode();
294         if (!ErrorNode)
295           return;
296 
297         SmallString<256> Msg;
298         llvm::raw_svector_ostream Out(Msg);
299         Out << "use of invalidated pointer '";
300         Call.getArgExpr(I)->printPretty(Out, /*Helper=*/nullptr,
301                                         C.getASTContext().getPrintingPolicy());
302         Out << "' in a function call";
303 
304         auto Report = std::make_unique<PathSensitiveBugReport>(
305             InvalidPtrBugType, Out.str(), ErrorNode);
306         Report->markInteresting(InvalidatedSymbolicBase);
307         Report->addRange(Call.getArgSourceRange(I));
308         C.emitReport(std::move(Report));
309       }
310     }
311   }
312 }
313 
314 // Obtain the environment pointer from 'main()', if present.
checkBeginFunction(CheckerContext & C) const315 void InvalidPtrChecker::checkBeginFunction(CheckerContext &C) const {
316   if (!C.inTopFrame())
317     return;
318 
319   const auto *FD = dyn_cast<FunctionDecl>(C.getLocationContext()->getDecl());
320   if (!FD || FD->param_size() != 3 || !FD->isMain())
321     return;
322 
323   ProgramStateRef State = C.getState();
324   const MemRegion *EnvpReg =
325       State->getRegion(FD->parameters()[2], C.getLocationContext());
326 
327   // Save the memory region pointed by the environment pointer parameter of
328   // 'main'.
329   C.addTransition(State->set<MainEnvPtrRegion>(EnvpReg));
330 }
331 
332 // Check if invalidated region is being dereferenced.
checkLocation(SVal Loc,bool isLoad,const Stmt * S,CheckerContext & C) const333 void InvalidPtrChecker::checkLocation(SVal Loc, bool isLoad, const Stmt *S,
334                                       CheckerContext &C) const {
335   ProgramStateRef State = C.getState();
336 
337   // Ignore memory operations involving 'non-invalidated' locations.
338   const MemRegion *InvalidatedSymbolicBase =
339       findInvalidatedSymbolicBase(State, Loc.getAsRegion());
340   if (!InvalidatedSymbolicBase)
341     return;
342 
343   ExplodedNode *ErrorNode = C.generateNonFatalErrorNode();
344   if (!ErrorNode)
345     return;
346 
347   auto Report = std::make_unique<PathSensitiveBugReport>(
348       InvalidPtrBugType, "dereferencing an invalid pointer", ErrorNode);
349   Report->markInteresting(InvalidatedSymbolicBase);
350   C.emitReport(std::move(Report));
351 }
352 
registerInvalidPtrChecker(CheckerManager & Mgr)353 void ento::registerInvalidPtrChecker(CheckerManager &Mgr) {
354   auto *Checker = Mgr.registerChecker<InvalidPtrChecker>();
355   Checker->InvalidatingGetEnv =
356       Mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker,
357                                                        "InvalidatingGetEnv");
358 }
359 
shouldRegisterInvalidPtrChecker(const CheckerManager &)360 bool ento::shouldRegisterInvalidPtrChecker(const CheckerManager &) {
361   return true;
362 }
363