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 |
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(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 | |
165 | 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>(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 | |
182 | 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>(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. |
234 | static 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. |
255 | 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>(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. |
315 | void 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. |
333 | 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, 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 | |
353 | void ento::registerInvalidPtrChecker(CheckerManager &Mgr) { |
354 | auto *Checker = Mgr.registerChecker<InvalidPtrChecker>(); |
355 | Checker->InvalidatingGetEnv = |
356 | Mgr.getAnalyzerOptions().getCheckerBooleanOption(C: Checker, |
357 | OptionName: "InvalidatingGetEnv" ); |
358 | } |
359 | |
360 | bool ento::shouldRegisterInvalidPtrChecker(const CheckerManager &) { |
361 | return true; |
362 | } |
363 | |