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