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
25using namespace clang;
26using namespace ento;
27
28namespace {
29
30class InvalidPtrChecker
31    : public Checker<check::Location, check::BeginFunction, check::PostCall> {
32private:
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
80public:
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
95private:
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
104REGISTER_SET_WITH_PROGRAMSTATE(InvalidMemoryRegions, const MemRegion *)
105
106// Stores the region of the environment pointer of 'main' (if present).
107REGISTER_TRAIT_WITH_PROGRAMSTATE(MainEnvPtrRegion, const MemRegion *)
108
109// Stores the regions of environments returned by getenv calls.
110REGISTER_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
114REGISTER_MAP_WITH_PROGRAMSTATE(PreviousCallResultMap, const FunctionDecl *,
115                               const MemRegion *)
116
117const 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
160void 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
177void 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  const auto *SymRegOfRetVal =
209      dyn_cast_or_null<SymbolicRegion>(RetVal.getAsRegion());
210  if (!SymRegOfRetVal)
211    return;
212
213  // Remember to this region.
214  const MemRegion *MR = SymRegOfRetVal->getBaseRegion();
215  State = State->set<PreviousCallResultMap>(FD, MR);
216
217  ExplodedNode *Node = C.addTransition(State, Note);
218  const NoteTag *PreviousCallNote = C.getNoteTag(
219      [this, MR](PathSensitiveBugReport &BR, llvm::raw_ostream &Out) {
220        if (!BR.isInteresting(MR) || &BR.getBugType() != &InvalidPtrBugType)
221          return;
222        Out << "previous function call was here";
223      });
224
225  C.addTransition(State, Node, PreviousCallNote);
226}
227
228// TODO: This seems really ugly. Simplify this.
229static const MemRegion *findInvalidatedSymbolicBase(ProgramStateRef State,
230                                                    const MemRegion *Reg) {
231  while (Reg) {
232    if (State->contains<InvalidMemoryRegions>(Reg))
233      return Reg;
234    const auto *SymBase = Reg->getSymbolicBase();
235    if (!SymBase)
236      break;
237    const auto *SRV = dyn_cast<SymbolRegionValue>(SymBase->getSymbol());
238    if (!SRV)
239      break;
240    Reg = SRV->getRegion();
241    if (const auto *VarReg = dyn_cast<VarRegion>(SRV->getRegion()))
242      Reg = VarReg;
243  }
244  return nullptr;
245}
246
247// Handle functions in EnvpInvalidatingFunctions, that invalidate environment
248// pointer from 'main()' Also, check if invalidated region is passed to a
249// function call as an argument.
250void InvalidPtrChecker::checkPostCall(const CallEvent &Call,
251                                      CheckerContext &C) const {
252
253  ProgramStateRef State = C.getState();
254
255  // Model 'getenv' calls
256  if (GetEnvCall.matches(Call)) {
257    const MemRegion *Region = Call.getReturnValue().getAsRegion();
258    if (Region) {
259      State = State->add<GetenvEnvPtrRegions>(Region);
260      C.addTransition(State);
261    }
262  }
263
264  // Check if function invalidates 'envp' argument of 'main'
265  if (const auto *Handler = EnvpInvalidatingFunctions.lookup(Call))
266    (this->**Handler)(Call, C);
267
268  // Check if function invalidates the result of previous call
269  if (const auto *Handler = PreviousCallInvalidatingFunctions.lookup(Call))
270    (this->**Handler)(Call, C);
271
272  // If pedantic mode is on, regard 'getenv' calls invalidating as well
273  if (InvalidatingGetEnv && GetEnvCall.matches(Call))
274    postPreviousReturnInvalidatingCall(Call, C);
275
276  // Check if one of the arguments of the function call is invalidated
277
278  // If call was inlined, don't report invalidated argument
279  if (C.wasInlined)
280    return;
281
282  for (unsigned I = 0, NumArgs = Call.getNumArgs(); I < NumArgs; ++I) {
283
284    if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(
285            Call.getArgSVal(I).getAsRegion())) {
286      if (const MemRegion *InvalidatedSymbolicBase =
287              findInvalidatedSymbolicBase(State, SR)) {
288        ExplodedNode *ErrorNode = C.generateNonFatalErrorNode();
289        if (!ErrorNode)
290          return;
291
292        SmallString<256> Msg;
293        llvm::raw_svector_ostream Out(Msg);
294        Out << "use of invalidated pointer '";
295        Call.getArgExpr(I)->printPretty(Out, /*Helper=*/nullptr,
296                                        C.getASTContext().getPrintingPolicy());
297        Out << "' in a function call";
298
299        auto Report = std::make_unique<PathSensitiveBugReport>(
300            InvalidPtrBugType, Out.str(), ErrorNode);
301        Report->markInteresting(InvalidatedSymbolicBase);
302        Report->addRange(Call.getArgSourceRange(I));
303        C.emitReport(std::move(Report));
304      }
305    }
306  }
307}
308
309// Obtain the environment pointer from 'main()', if present.
310void InvalidPtrChecker::checkBeginFunction(CheckerContext &C) const {
311  if (!C.inTopFrame())
312    return;
313
314  const auto *FD = dyn_cast<FunctionDecl>(C.getLocationContext()->getDecl());
315  if (!FD || FD->param_size() != 3 || !FD->isMain())
316    return;
317
318  ProgramStateRef State = C.getState();
319  const MemRegion *EnvpReg =
320      State->getRegion(FD->parameters()[2], C.getLocationContext());
321
322  // Save the memory region pointed by the environment pointer parameter of
323  // 'main'.
324  C.addTransition(State->set<MainEnvPtrRegion>(EnvpReg));
325}
326
327// Check if invalidated region is being dereferenced.
328void InvalidPtrChecker::checkLocation(SVal Loc, bool isLoad, const Stmt *S,
329                                      CheckerContext &C) const {
330  ProgramStateRef State = C.getState();
331
332  // Ignore memory operations involving 'non-invalidated' locations.
333  const MemRegion *InvalidatedSymbolicBase =
334      findInvalidatedSymbolicBase(State, Loc.getAsRegion());
335  if (!InvalidatedSymbolicBase)
336    return;
337
338  ExplodedNode *ErrorNode = C.generateNonFatalErrorNode();
339  if (!ErrorNode)
340    return;
341
342  auto Report = std::make_unique<PathSensitiveBugReport>(
343      InvalidPtrBugType, "dereferencing an invalid pointer", ErrorNode);
344  Report->markInteresting(InvalidatedSymbolicBase);
345  C.emitReport(std::move(Report));
346}
347
348void ento::registerInvalidPtrChecker(CheckerManager &Mgr) {
349  auto *Checker = Mgr.registerChecker<InvalidPtrChecker>();
350  Checker->InvalidatingGetEnv =
351      Mgr.getAnalyzerOptions().getCheckerBooleanOption(Checker,
352                                                       "InvalidatingGetEnv");
353}
354
355bool ento::shouldRegisterInvalidPtrChecker(const CheckerManager &) {
356  return true;
357}
358