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{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
85public:
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
100private:
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
109REGISTER_SET_WITH_PROGRAMSTATE(InvalidMemoryRegions, const MemRegion *)
110
111// Stores the region of the environment pointer of 'main' (if present).
112REGISTER_TRAIT_WITH_PROGRAMSTATE(MainEnvPtrRegion, const MemRegion *)
113
114// Stores the regions of environments returned by getenv calls.
115REGISTER_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
119REGISTER_MAP_WITH_PROGRAMSTATE(PreviousCallResultMap, const FunctionDecl *,
120 const MemRegion *)
121
122const 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(Cb: [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(R: MainRegion)) {
141 BR.markNotInteresting(R: MainRegion);
142 InvalidLocationNames.push_back(Elt: "the environment parameter of 'main'");
143 }
144 bool InterestingGetenvFound = false;
145 for (const MemRegion *MR : GetenvRegions) {
146 if (BR.isInteresting(R: MR)) {
147 BR.markNotInteresting(R: MR);
148 if (!InterestingGetenvFound) {
149 InterestingGetenvFound = true;
150 InvalidLocationNames.push_back(
151 Elt: "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
165void 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>(K: MainEnvPtr);
172 for (const MemRegion *EnvPtr : State->get<GetenvEnvPtrRegions>())
173 State = State->add<InvalidMemoryRegions>(K: EnvPtr);
174
175 StringRef FunctionName = Call.getCalleeIdentifier()->getName();
176 const NoteTag *InvalidationNote =
177 createEnvInvalidationNote(C, State, FunctionName);
178
179 C.addTransition(State, Tag: InvalidationNote);
180}
181
182void 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>(Val: 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>(key: FD)) {
191 const MemRegion *PrevReg = *Reg;
192 State = State->add<InvalidMemoryRegions>(K: PrevReg);
193 Note = C.getNoteTag(Cb: [this, PrevReg, FD](PathSensitiveBugReport &BR,
194 llvm::raw_ostream &Out) {
195 if (!BR.isInteresting(R: PrevReg) || &BR.getBugType() != &InvalidPtrBugType)
196 return;
197 Out << '\'';
198 FD->getNameForDiagnostic(OS&: Out, Policy: FD->getASTContext().getLangOpts(), Qualified: true);
199 Out << "' call may invalidate the result of the previous " << '\'';
200 FD->getNameForDiagnostic(OS&: Out, Policy: FD->getASTContext().getLangOpts(), Qualified: true);
201 Out << '\'';
202 });
203 }
204
205 const LocationContext *LCtx = C.getLocationContext();
206 const auto *CE = cast<CallExpr>(Val: Call.getOriginExpr());
207
208 // Function call will return a pointer to the new symbolic region.
209 DefinedOrUnknownSVal RetVal = C.getSValBuilder().conjureSymbolVal(
210 stmt: CE, LCtx, type: CE->getType(), visitCount: C.blockCount());
211 State = State->BindExpr(S: CE, LCtx, V: RetVal);
212
213 const auto *SymRegOfRetVal =
214 dyn_cast_or_null<SymbolicRegion>(Val: RetVal.getAsRegion());
215 if (!SymRegOfRetVal)
216 return;
217
218 // Remember to this region.
219 const MemRegion *MR = SymRegOfRetVal->getBaseRegion();
220 State = State->set<PreviousCallResultMap>(K: FD, E: MR);
221
222 ExplodedNode *Node = C.addTransition(State, Tag: Note);
223 const NoteTag *PreviousCallNote = C.getNoteTag(
224 Cb: [this, MR](PathSensitiveBugReport &BR, llvm::raw_ostream &Out) {
225 if (!BR.isInteresting(R: MR) || &BR.getBugType() != &InvalidPtrBugType)
226 return;
227 Out << "previous function call was here";
228 });
229
230 C.addTransition(State, Pred: Node, Tag: PreviousCallNote);
231}
232
233// TODO: This seems really ugly. Simplify this.
234static const MemRegion *findInvalidatedSymbolicBase(ProgramStateRef State,
235 const MemRegion *Reg) {
236 while (Reg) {
237 if (State->contains<InvalidMemoryRegions>(key: Reg))
238 return Reg;
239 const auto *SymBase = Reg->getSymbolicBase();
240 if (!SymBase)
241 break;
242 const auto *SRV = dyn_cast<SymbolRegionValue>(Val: SymBase->getSymbol());
243 if (!SRV)
244 break;
245 Reg = SRV->getRegion();
246 if (const auto *VarReg = dyn_cast<VarRegion>(Val: 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.
255void 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>(K: 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 Val: Call.getArgSVal(Index: I).getAsRegion())) {
291 if (const MemRegion *InvalidatedSymbolicBase =
292 findInvalidatedSymbolicBase(State, Reg: 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(Index: I)->printPretty(OS&: Out, /*Helper=*/nullptr,
301 Policy: C.getASTContext().getPrintingPolicy());
302 Out << "' in a function call";
303
304 auto Report = std::make_unique<PathSensitiveBugReport>(
305 args: InvalidPtrBugType, args: Out.str(), args&: ErrorNode);
306 Report->markInteresting(R: InvalidatedSymbolicBase);
307 Report->addRange(R: Call.getArgSourceRange(Index: I));
308 C.emitReport(R: std::move(Report));
309 }
310 }
311 }
312}
313
314// Obtain the environment pointer from 'main()', if present.
315void InvalidPtrChecker::checkBeginFunction(CheckerContext &C) const {
316 if (!C.inTopFrame())
317 return;
318
319 const auto *FD = dyn_cast<FunctionDecl>(Val: 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(D: FD->parameters()[2], LC: C.getLocationContext());
326
327 // Save the memory region pointed by the environment pointer parameter of
328 // 'main'.
329 C.addTransition(State: State->set<MainEnvPtrRegion>(EnvpReg));
330}
331
332// Check if invalidated region is being dereferenced.
333void 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, Reg: 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 args: InvalidPtrBugType, args: "dereferencing an invalid pointer", args&: ErrorNode);
349 Report->markInteresting(R: InvalidatedSymbolicBase);
350 C.emitReport(R: std::move(Report));
351}
352
353void ento::registerInvalidPtrChecker(CheckerManager &Mgr) {
354 auto *Checker = Mgr.registerChecker<InvalidPtrChecker>();
355 Checker->InvalidatingGetEnv =
356 Mgr.getAnalyzerOptions().getCheckerBooleanOption(C: Checker,
357 OptionName: "InvalidatingGetEnv");
358}
359
360bool ento::shouldRegisterInvalidPtrChecker(const CheckerManager &) {
361 return true;
362}
363