1 | //= UnixAPIChecker.h - Checks preconditions for various Unix APIs --*- 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 defines UnixAPIChecker, which is an assortment of checks on calls |
10 | // to various, widely used UNIX/Posix functions. |
11 | // |
12 | //===----------------------------------------------------------------------===// |
13 | |
14 | #include "clang/Basic/TargetInfo.h" |
15 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
16 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
17 | #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" |
18 | #include "clang/StaticAnalyzer/Core/Checker.h" |
19 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
20 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
21 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
22 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" |
23 | #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" |
24 | #include "llvm/ADT/STLExtras.h" |
25 | #include "llvm/ADT/SmallString.h" |
26 | #include "llvm/ADT/StringExtras.h" |
27 | #include "llvm/Support/raw_ostream.h" |
28 | #include <optional> |
29 | |
30 | using namespace clang; |
31 | using namespace ento; |
32 | |
33 | enum class OpenVariant { |
34 | /// The standard open() call: |
35 | /// int open(const char *path, int oflag, ...); |
36 | Open, |
37 | |
38 | /// The variant taking a directory file descriptor and a relative path: |
39 | /// int openat(int fd, const char *path, int oflag, ...); |
40 | OpenAt |
41 | }; |
42 | |
43 | namespace { |
44 | |
45 | class UnixAPIMisuseChecker |
46 | : public Checker<check::PreCall, check::ASTDecl<TranslationUnitDecl>> { |
47 | const BugType BT_open{this, "Improper use of 'open'" , categories::UnixAPI}; |
48 | const BugType BT_getline{this, "Improper use of getdelim" , |
49 | categories::UnixAPI}; |
50 | const BugType BT_pthreadOnce{this, "Improper use of 'pthread_once'" , |
51 | categories::UnixAPI}; |
52 | const BugType BT_ArgumentNull{this, "NULL pointer" , categories::UnixAPI}; |
53 | mutable std::optional<uint64_t> Val_O_CREAT; |
54 | |
55 | ProgramStateRef |
56 | EnsurePtrNotNull(SVal PtrVal, const Expr *PtrExpr, CheckerContext &C, |
57 | ProgramStateRef State, const StringRef PtrDescr, |
58 | std::optional<std::reference_wrapper<const BugType>> BT = |
59 | std::nullopt) const; |
60 | |
61 | ProgramStateRef EnsureGetdelimBufferAndSizeCorrect( |
62 | SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr, |
63 | const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const; |
64 | |
65 | public: |
66 | void checkASTDecl(const TranslationUnitDecl *TU, AnalysisManager &Mgr, |
67 | BugReporter &BR) const; |
68 | |
69 | void checkPreCall(const CallEvent &Call, CheckerContext &C) const; |
70 | |
71 | void CheckOpen(CheckerContext &C, const CallEvent &Call) const; |
72 | void CheckOpenAt(CheckerContext &C, const CallEvent &Call) const; |
73 | void CheckGetDelim(CheckerContext &C, const CallEvent &Call) const; |
74 | void CheckPthreadOnce(CheckerContext &C, const CallEvent &Call) const; |
75 | |
76 | void CheckOpenVariant(CheckerContext &C, const CallEvent &Call, |
77 | OpenVariant Variant) const; |
78 | |
79 | void ReportOpenBug(CheckerContext &C, ProgramStateRef State, const char *Msg, |
80 | SourceRange SR) const; |
81 | }; |
82 | |
83 | class UnixAPIPortabilityChecker : public Checker< check::PreStmt<CallExpr> > { |
84 | public: |
85 | void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; |
86 | |
87 | private: |
88 | const BugType BT_mallocZero{ |
89 | this, "Undefined allocation of 0 bytes (CERT MEM04-C; CWE-131)" , |
90 | categories::UnixAPI}; |
91 | |
92 | void CheckCallocZero(CheckerContext &C, const CallExpr *CE) const; |
93 | void CheckMallocZero(CheckerContext &C, const CallExpr *CE) const; |
94 | void CheckReallocZero(CheckerContext &C, const CallExpr *CE) const; |
95 | void CheckReallocfZero(CheckerContext &C, const CallExpr *CE) const; |
96 | void CheckAllocaZero(CheckerContext &C, const CallExpr *CE) const; |
97 | void CheckAllocaWithAlignZero(CheckerContext &C, const CallExpr *CE) const; |
98 | void CheckVallocZero(CheckerContext &C, const CallExpr *CE) const; |
99 | |
100 | bool ReportZeroByteAllocation(CheckerContext &C, |
101 | ProgramStateRef falseState, |
102 | const Expr *arg, |
103 | const char *fn_name) const; |
104 | void BasicAllocationCheck(CheckerContext &C, |
105 | const CallExpr *CE, |
106 | const unsigned numArgs, |
107 | const unsigned sizeArg, |
108 | const char *fn) const; |
109 | }; |
110 | |
111 | } // end anonymous namespace |
112 | |
113 | ProgramStateRef UnixAPIMisuseChecker::EnsurePtrNotNull( |
114 | SVal PtrVal, const Expr *PtrExpr, CheckerContext &C, ProgramStateRef State, |
115 | const StringRef PtrDescr, |
116 | std::optional<std::reference_wrapper<const BugType>> BT) const { |
117 | const auto Ptr = PtrVal.getAs<DefinedSVal>(); |
118 | if (!Ptr) |
119 | return State; |
120 | |
121 | const auto [PtrNotNull, PtrNull] = State->assume(Cond: *Ptr); |
122 | if (!PtrNotNull && PtrNull) { |
123 | if (ExplodedNode *N = C.generateErrorNode(State: PtrNull)) { |
124 | auto R = std::make_unique<PathSensitiveBugReport>( |
125 | args: BT.value_or(u: std::cref(t: BT_ArgumentNull)), |
126 | args: (PtrDescr + " pointer might be NULL." ).str(), args&: N); |
127 | if (PtrExpr) |
128 | bugreporter::trackExpressionValue(N, E: PtrExpr, R&: *R); |
129 | C.emitReport(R: std::move(R)); |
130 | } |
131 | return nullptr; |
132 | } |
133 | |
134 | return PtrNotNull; |
135 | } |
136 | |
137 | void UnixAPIMisuseChecker::checkASTDecl(const TranslationUnitDecl *TU, |
138 | AnalysisManager &Mgr, |
139 | BugReporter &) const { |
140 | // The definition of O_CREAT is platform specific. |
141 | // Try to get the macro value from the preprocessor. |
142 | Val_O_CREAT = tryExpandAsInteger(Macro: "O_CREAT" , PP: Mgr.getPreprocessor()); |
143 | // If we failed, fall-back to known values. |
144 | if (!Val_O_CREAT) { |
145 | if (TU->getASTContext().getTargetInfo().getTriple().getVendor() == |
146 | llvm::Triple::Apple) |
147 | Val_O_CREAT = 0x0200; |
148 | } |
149 | } |
150 | |
151 | //===----------------------------------------------------------------------===// |
152 | // "open" (man 2 open) |
153 | //===----------------------------------------------------------------------===/ |
154 | |
155 | void UnixAPIMisuseChecker::checkPreCall(const CallEvent &Call, |
156 | CheckerContext &C) const { |
157 | const FunctionDecl *FD = dyn_cast_if_present<FunctionDecl>(Val: Call.getDecl()); |
158 | if (!FD || FD->getKind() != Decl::Function) |
159 | return; |
160 | |
161 | // Don't treat functions in namespaces with the same name a Unix function |
162 | // as a call to the Unix function. |
163 | const DeclContext *NamespaceCtx = FD->getEnclosingNamespaceContext(); |
164 | if (isa_and_nonnull<NamespaceDecl>(Val: NamespaceCtx)) |
165 | return; |
166 | |
167 | StringRef FName = C.getCalleeName(FunDecl: FD); |
168 | if (FName.empty()) |
169 | return; |
170 | |
171 | if (FName == "open" ) |
172 | CheckOpen(C, Call); |
173 | |
174 | else if (FName == "openat" ) |
175 | CheckOpenAt(C, Call); |
176 | |
177 | else if (FName == "pthread_once" ) |
178 | CheckPthreadOnce(C, Call); |
179 | |
180 | else if (is_contained(Set: {"getdelim" , "getline" }, Element: FName)) |
181 | CheckGetDelim(C, Call); |
182 | } |
183 | void UnixAPIMisuseChecker::ReportOpenBug(CheckerContext &C, |
184 | ProgramStateRef State, |
185 | const char *Msg, |
186 | SourceRange SR) const { |
187 | ExplodedNode *N = C.generateErrorNode(State); |
188 | if (!N) |
189 | return; |
190 | |
191 | auto Report = std::make_unique<PathSensitiveBugReport>(args: BT_open, args&: Msg, args&: N); |
192 | Report->addRange(R: SR); |
193 | C.emitReport(R: std::move(Report)); |
194 | } |
195 | |
196 | void UnixAPIMisuseChecker::CheckOpen(CheckerContext &C, |
197 | const CallEvent &Call) const { |
198 | CheckOpenVariant(C, Call, Variant: OpenVariant::Open); |
199 | } |
200 | |
201 | void UnixAPIMisuseChecker::CheckOpenAt(CheckerContext &C, |
202 | const CallEvent &Call) const { |
203 | CheckOpenVariant(C, Call, Variant: OpenVariant::OpenAt); |
204 | } |
205 | |
206 | void UnixAPIMisuseChecker::CheckOpenVariant(CheckerContext &C, |
207 | const CallEvent &Call, |
208 | OpenVariant Variant) const { |
209 | // The index of the argument taking the flags open flags (O_RDONLY, |
210 | // O_WRONLY, O_CREAT, etc.), |
211 | unsigned int FlagsArgIndex; |
212 | const char *VariantName; |
213 | switch (Variant) { |
214 | case OpenVariant::Open: |
215 | FlagsArgIndex = 1; |
216 | VariantName = "open" ; |
217 | break; |
218 | case OpenVariant::OpenAt: |
219 | FlagsArgIndex = 2; |
220 | VariantName = "openat" ; |
221 | break; |
222 | }; |
223 | |
224 | // All calls should at least provide arguments up to the 'flags' parameter. |
225 | unsigned int MinArgCount = FlagsArgIndex + 1; |
226 | |
227 | // If the flags has O_CREAT set then open/openat() require an additional |
228 | // argument specifying the file mode (permission bits) for the created file. |
229 | unsigned int CreateModeArgIndex = FlagsArgIndex + 1; |
230 | |
231 | // The create mode argument should be the last argument. |
232 | unsigned int MaxArgCount = CreateModeArgIndex + 1; |
233 | |
234 | ProgramStateRef state = C.getState(); |
235 | |
236 | if (Call.getNumArgs() < MinArgCount) { |
237 | // The frontend should issue a warning for this case. Just return. |
238 | return; |
239 | } else if (Call.getNumArgs() == MaxArgCount) { |
240 | const Expr *Arg = Call.getArgExpr(Index: CreateModeArgIndex); |
241 | QualType QT = Arg->getType(); |
242 | if (!QT->isIntegerType()) { |
243 | SmallString<256> SBuf; |
244 | llvm::raw_svector_ostream OS(SBuf); |
245 | OS << "The " << CreateModeArgIndex + 1 |
246 | << llvm::getOrdinalSuffix(Val: CreateModeArgIndex + 1) |
247 | << " argument to '" << VariantName << "' is not an integer" ; |
248 | |
249 | ReportOpenBug(C, State: state, |
250 | Msg: SBuf.c_str(), |
251 | SR: Arg->getSourceRange()); |
252 | return; |
253 | } |
254 | } else if (Call.getNumArgs() > MaxArgCount) { |
255 | SmallString<256> SBuf; |
256 | llvm::raw_svector_ostream OS(SBuf); |
257 | OS << "Call to '" << VariantName << "' with more than " << MaxArgCount |
258 | << " arguments" ; |
259 | |
260 | ReportOpenBug(C, State: state, Msg: SBuf.c_str(), |
261 | SR: Call.getArgExpr(Index: MaxArgCount)->getSourceRange()); |
262 | return; |
263 | } |
264 | |
265 | if (!Val_O_CREAT) { |
266 | return; |
267 | } |
268 | |
269 | // Now check if oflags has O_CREAT set. |
270 | const Expr *oflagsEx = Call.getArgExpr(Index: FlagsArgIndex); |
271 | const SVal V = Call.getArgSVal(Index: FlagsArgIndex); |
272 | if (!isa<NonLoc>(Val: V)) { |
273 | // The case where 'V' can be a location can only be due to a bad header, |
274 | // so in this case bail out. |
275 | return; |
276 | } |
277 | NonLoc oflags = V.castAs<NonLoc>(); |
278 | NonLoc ocreateFlag = C.getSValBuilder() |
279 | .makeIntVal(integer: *Val_O_CREAT, type: oflagsEx->getType()) |
280 | .castAs<NonLoc>(); |
281 | SVal maskedFlagsUC = C.getSValBuilder().evalBinOpNN(state, op: BO_And, |
282 | lhs: oflags, rhs: ocreateFlag, |
283 | resultTy: oflagsEx->getType()); |
284 | if (maskedFlagsUC.isUnknownOrUndef()) |
285 | return; |
286 | DefinedSVal maskedFlags = maskedFlagsUC.castAs<DefinedSVal>(); |
287 | |
288 | // Check if maskedFlags is non-zero. |
289 | ProgramStateRef trueState, falseState; |
290 | std::tie(args&: trueState, args&: falseState) = state->assume(Cond: maskedFlags); |
291 | |
292 | // Only emit an error if the value of 'maskedFlags' is properly |
293 | // constrained; |
294 | if (!(trueState && !falseState)) |
295 | return; |
296 | |
297 | if (Call.getNumArgs() < MaxArgCount) { |
298 | SmallString<256> SBuf; |
299 | llvm::raw_svector_ostream OS(SBuf); |
300 | OS << "Call to '" << VariantName << "' requires a " |
301 | << CreateModeArgIndex + 1 |
302 | << llvm::getOrdinalSuffix(Val: CreateModeArgIndex + 1) |
303 | << " argument when the 'O_CREAT' flag is set" ; |
304 | ReportOpenBug(C, State: trueState, |
305 | Msg: SBuf.c_str(), |
306 | SR: oflagsEx->getSourceRange()); |
307 | } |
308 | } |
309 | |
310 | //===----------------------------------------------------------------------===// |
311 | // getdelim and getline |
312 | //===----------------------------------------------------------------------===// |
313 | |
314 | ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect( |
315 | SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr, |
316 | const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const { |
317 | static constexpr llvm::StringLiteral SizeGreaterThanBufferSize = |
318 | "The buffer from the first argument is smaller than the size " |
319 | "specified by the second parameter" ; |
320 | static constexpr llvm::StringLiteral SizeUndef = |
321 | "The buffer from the first argument is not NULL, but the size specified " |
322 | "by the second parameter is undefined." ; |
323 | |
324 | auto EmitBugReport = [this, &C, SizePtrExpr, LinePtrPtrExpr]( |
325 | ProgramStateRef BugState, StringRef ErrMsg) { |
326 | if (ExplodedNode *N = C.generateErrorNode(State: BugState)) { |
327 | auto R = std::make_unique<PathSensitiveBugReport>(args: BT_getline, args&: ErrMsg, args&: N); |
328 | bugreporter::trackExpressionValue(N, E: SizePtrExpr, R&: *R); |
329 | bugreporter::trackExpressionValue(N, E: LinePtrPtrExpr, R&: *R); |
330 | C.emitReport(R: std::move(R)); |
331 | } |
332 | }; |
333 | |
334 | // We have a pointer to a pointer to the buffer, and a pointer to the size. |
335 | // We want what they point at. |
336 | auto LinePtrSVal = getPointeeVal(PtrSVal: LinePtrPtrSVal, State)->getAs<DefinedSVal>(); |
337 | auto NSVal = getPointeeVal(PtrSVal: SizePtrSVal, State); |
338 | if (!LinePtrSVal || !NSVal || NSVal->isUnknown()) |
339 | return nullptr; |
340 | |
341 | assert(LinePtrPtrExpr && SizePtrExpr); |
342 | |
343 | const auto [LinePtrNotNull, LinePtrNull] = State->assume(Cond: *LinePtrSVal); |
344 | if (LinePtrNotNull && !LinePtrNull) { |
345 | // If `*lineptr` is not null, but `*n` is undefined, there is UB. |
346 | if (NSVal->isUndef()) { |
347 | EmitBugReport(LinePtrNotNull, SizeUndef); |
348 | return nullptr; |
349 | } |
350 | |
351 | // If it is defined, and known, its size must be less than or equal to |
352 | // the buffer size. |
353 | auto NDefSVal = NSVal->getAs<DefinedSVal>(); |
354 | auto &SVB = C.getSValBuilder(); |
355 | auto LineBufSize = |
356 | getDynamicExtent(State: LinePtrNotNull, MR: LinePtrSVal->getAsRegion(), SVB); |
357 | auto LineBufSizeGtN = SVB.evalBinOp(state: LinePtrNotNull, op: BO_GE, lhs: LineBufSize, |
358 | rhs: *NDefSVal, type: SVB.getConditionType()) |
359 | .getAs<DefinedOrUnknownSVal>(); |
360 | if (!LineBufSizeGtN) |
361 | return LinePtrNotNull; |
362 | if (auto LineBufSizeOk = LinePtrNotNull->assume(Cond: *LineBufSizeGtN, Assumption: true)) |
363 | return LineBufSizeOk; |
364 | |
365 | EmitBugReport(LinePtrNotNull, SizeGreaterThanBufferSize); |
366 | return nullptr; |
367 | } |
368 | return State; |
369 | } |
370 | |
371 | void UnixAPIMisuseChecker::CheckGetDelim(CheckerContext &C, |
372 | const CallEvent &Call) const { |
373 | ProgramStateRef State = C.getState(); |
374 | |
375 | // The parameter `n` must not be NULL. |
376 | SVal SizePtrSval = Call.getArgSVal(Index: 1); |
377 | State = EnsurePtrNotNull(PtrVal: SizePtrSval, PtrExpr: Call.getArgExpr(Index: 1), C, State, PtrDescr: "Size" ); |
378 | if (!State) |
379 | return; |
380 | |
381 | // The parameter `lineptr` must not be NULL. |
382 | SVal LinePtrPtrSVal = Call.getArgSVal(Index: 0); |
383 | State = |
384 | EnsurePtrNotNull(PtrVal: LinePtrPtrSVal, PtrExpr: Call.getArgExpr(Index: 0), C, State, PtrDescr: "Line" ); |
385 | if (!State) |
386 | return; |
387 | |
388 | State = EnsureGetdelimBufferAndSizeCorrect(LinePtrPtrSVal, SizePtrSVal: SizePtrSval, |
389 | LinePtrPtrExpr: Call.getArgExpr(Index: 0), |
390 | SizePtrExpr: Call.getArgExpr(Index: 1), C, State); |
391 | if (!State) |
392 | return; |
393 | |
394 | C.addTransition(State); |
395 | } |
396 | |
397 | //===----------------------------------------------------------------------===// |
398 | // pthread_once |
399 | //===----------------------------------------------------------------------===// |
400 | |
401 | void UnixAPIMisuseChecker::CheckPthreadOnce(CheckerContext &C, |
402 | const CallEvent &Call) const { |
403 | |
404 | // This is similar to 'CheckDispatchOnce' in the MacOSXAPIChecker. |
405 | // They can possibly be refactored. |
406 | |
407 | if (Call.getNumArgs() < 1) |
408 | return; |
409 | |
410 | // Check if the first argument is stack allocated. If so, issue a warning |
411 | // because that's likely to be bad news. |
412 | ProgramStateRef state = C.getState(); |
413 | const MemRegion *R = Call.getArgSVal(Index: 0).getAsRegion(); |
414 | if (!R || !isa<StackSpaceRegion>(Val: R->getMemorySpace())) |
415 | return; |
416 | |
417 | ExplodedNode *N = C.generateErrorNode(State: state); |
418 | if (!N) |
419 | return; |
420 | |
421 | SmallString<256> S; |
422 | llvm::raw_svector_ostream os(S); |
423 | os << "Call to 'pthread_once' uses" ; |
424 | if (const VarRegion *VR = dyn_cast<VarRegion>(Val: R)) |
425 | os << " the local variable '" << VR->getDecl()->getName() << '\''; |
426 | else |
427 | os << " stack allocated memory" ; |
428 | os << " for the \"control\" value. Using such transient memory for " |
429 | "the control value is potentially dangerous." ; |
430 | if (isa<VarRegion>(Val: R) && isa<StackLocalsSpaceRegion>(Val: R->getMemorySpace())) |
431 | os << " Perhaps you intended to declare the variable as 'static'?" ; |
432 | |
433 | auto report = |
434 | std::make_unique<PathSensitiveBugReport>(args: BT_pthreadOnce, args: os.str(), args&: N); |
435 | report->addRange(R: Call.getArgExpr(Index: 0)->getSourceRange()); |
436 | C.emitReport(R: std::move(report)); |
437 | } |
438 | |
439 | //===----------------------------------------------------------------------===// |
440 | // "calloc", "malloc", "realloc", "reallocf", "alloca" and "valloc" |
441 | // with allocation size 0 |
442 | //===----------------------------------------------------------------------===// |
443 | |
444 | // FIXME: Eventually these should be rolled into the MallocChecker, but right now |
445 | // they're more basic and valuable for widespread use. |
446 | |
447 | // Returns true if we try to do a zero byte allocation, false otherwise. |
448 | // Fills in trueState and falseState. |
449 | static bool IsZeroByteAllocation(ProgramStateRef state, |
450 | const SVal argVal, |
451 | ProgramStateRef *trueState, |
452 | ProgramStateRef *falseState) { |
453 | std::tie(args&: *trueState, args&: *falseState) = |
454 | state->assume(Cond: argVal.castAs<DefinedSVal>()); |
455 | |
456 | return (*falseState && !*trueState); |
457 | } |
458 | |
459 | // Generates an error report, indicating that the function whose name is given |
460 | // will perform a zero byte allocation. |
461 | // Returns false if an error occurred, true otherwise. |
462 | bool UnixAPIPortabilityChecker::ReportZeroByteAllocation( |
463 | CheckerContext &C, |
464 | ProgramStateRef falseState, |
465 | const Expr *arg, |
466 | const char *fn_name) const { |
467 | ExplodedNode *N = C.generateErrorNode(State: falseState); |
468 | if (!N) |
469 | return false; |
470 | |
471 | SmallString<256> S; |
472 | llvm::raw_svector_ostream os(S); |
473 | os << "Call to '" << fn_name << "' has an allocation size of 0 bytes" ; |
474 | auto report = |
475 | std::make_unique<PathSensitiveBugReport>(args: BT_mallocZero, args: os.str(), args&: N); |
476 | |
477 | report->addRange(R: arg->getSourceRange()); |
478 | bugreporter::trackExpressionValue(N, E: arg, R&: *report); |
479 | C.emitReport(R: std::move(report)); |
480 | |
481 | return true; |
482 | } |
483 | |
484 | // Does a basic check for 0-sized allocations suitable for most of the below |
485 | // functions (modulo "calloc") |
486 | void UnixAPIPortabilityChecker::BasicAllocationCheck(CheckerContext &C, |
487 | const CallExpr *CE, |
488 | const unsigned numArgs, |
489 | const unsigned sizeArg, |
490 | const char *fn) const { |
491 | // Check for the correct number of arguments. |
492 | if (CE->getNumArgs() != numArgs) |
493 | return; |
494 | |
495 | // Check if the allocation size is 0. |
496 | ProgramStateRef state = C.getState(); |
497 | ProgramStateRef trueState = nullptr, falseState = nullptr; |
498 | const Expr *arg = CE->getArg(Arg: sizeArg); |
499 | SVal argVal = C.getSVal(S: arg); |
500 | |
501 | if (argVal.isUnknownOrUndef()) |
502 | return; |
503 | |
504 | // Is the value perfectly constrained to zero? |
505 | if (IsZeroByteAllocation(state, argVal, trueState: &trueState, falseState: &falseState)) { |
506 | (void) ReportZeroByteAllocation(C, falseState, arg, fn_name: fn); |
507 | return; |
508 | } |
509 | // Assume the value is non-zero going forward. |
510 | assert(trueState); |
511 | if (trueState != state) |
512 | C.addTransition(State: trueState); |
513 | } |
514 | |
515 | void UnixAPIPortabilityChecker::CheckCallocZero(CheckerContext &C, |
516 | const CallExpr *CE) const { |
517 | unsigned int nArgs = CE->getNumArgs(); |
518 | if (nArgs != 2) |
519 | return; |
520 | |
521 | ProgramStateRef state = C.getState(); |
522 | ProgramStateRef trueState = nullptr, falseState = nullptr; |
523 | |
524 | unsigned int i; |
525 | for (i = 0; i < nArgs; i++) { |
526 | const Expr *arg = CE->getArg(Arg: i); |
527 | SVal argVal = C.getSVal(S: arg); |
528 | if (argVal.isUnknownOrUndef()) { |
529 | if (i == 0) |
530 | continue; |
531 | else |
532 | return; |
533 | } |
534 | |
535 | if (IsZeroByteAllocation(state, argVal, trueState: &trueState, falseState: &falseState)) { |
536 | if (ReportZeroByteAllocation(C, falseState, arg, fn_name: "calloc" )) |
537 | return; |
538 | else if (i == 0) |
539 | continue; |
540 | else |
541 | return; |
542 | } |
543 | } |
544 | |
545 | // Assume the value is non-zero going forward. |
546 | assert(trueState); |
547 | if (trueState != state) |
548 | C.addTransition(State: trueState); |
549 | } |
550 | |
551 | void UnixAPIPortabilityChecker::CheckMallocZero(CheckerContext &C, |
552 | const CallExpr *CE) const { |
553 | BasicAllocationCheck(C, CE, numArgs: 1, sizeArg: 0, fn: "malloc" ); |
554 | } |
555 | |
556 | void UnixAPIPortabilityChecker::CheckReallocZero(CheckerContext &C, |
557 | const CallExpr *CE) const { |
558 | BasicAllocationCheck(C, CE, numArgs: 2, sizeArg: 1, fn: "realloc" ); |
559 | } |
560 | |
561 | void UnixAPIPortabilityChecker::CheckReallocfZero(CheckerContext &C, |
562 | const CallExpr *CE) const { |
563 | BasicAllocationCheck(C, CE, numArgs: 2, sizeArg: 1, fn: "reallocf" ); |
564 | } |
565 | |
566 | void UnixAPIPortabilityChecker::CheckAllocaZero(CheckerContext &C, |
567 | const CallExpr *CE) const { |
568 | BasicAllocationCheck(C, CE, numArgs: 1, sizeArg: 0, fn: "alloca" ); |
569 | } |
570 | |
571 | void UnixAPIPortabilityChecker::CheckAllocaWithAlignZero( |
572 | CheckerContext &C, |
573 | const CallExpr *CE) const { |
574 | BasicAllocationCheck(C, CE, numArgs: 2, sizeArg: 0, fn: "__builtin_alloca_with_align" ); |
575 | } |
576 | |
577 | void UnixAPIPortabilityChecker::CheckVallocZero(CheckerContext &C, |
578 | const CallExpr *CE) const { |
579 | BasicAllocationCheck(C, CE, numArgs: 1, sizeArg: 0, fn: "valloc" ); |
580 | } |
581 | |
582 | void UnixAPIPortabilityChecker::checkPreStmt(const CallExpr *CE, |
583 | CheckerContext &C) const { |
584 | const FunctionDecl *FD = C.getCalleeDecl(CE); |
585 | if (!FD || FD->getKind() != Decl::Function) |
586 | return; |
587 | |
588 | // Don't treat functions in namespaces with the same name a Unix function |
589 | // as a call to the Unix function. |
590 | const DeclContext *NamespaceCtx = FD->getEnclosingNamespaceContext(); |
591 | if (isa_and_nonnull<NamespaceDecl>(Val: NamespaceCtx)) |
592 | return; |
593 | |
594 | StringRef FName = C.getCalleeName(FunDecl: FD); |
595 | if (FName.empty()) |
596 | return; |
597 | |
598 | if (FName == "calloc" ) |
599 | CheckCallocZero(C, CE); |
600 | |
601 | else if (FName == "malloc" ) |
602 | CheckMallocZero(C, CE); |
603 | |
604 | else if (FName == "realloc" ) |
605 | CheckReallocZero(C, CE); |
606 | |
607 | else if (FName == "reallocf" ) |
608 | CheckReallocfZero(C, CE); |
609 | |
610 | else if (FName == "alloca" || FName == "__builtin_alloca" ) |
611 | CheckAllocaZero(C, CE); |
612 | |
613 | else if (FName == "__builtin_alloca_with_align" ) |
614 | CheckAllocaWithAlignZero(C, CE); |
615 | |
616 | else if (FName == "valloc" ) |
617 | CheckVallocZero(C, CE); |
618 | } |
619 | |
620 | //===----------------------------------------------------------------------===// |
621 | // Registration. |
622 | //===----------------------------------------------------------------------===// |
623 | |
624 | #define REGISTER_CHECKER(CHECKERNAME) \ |
625 | void ento::register##CHECKERNAME(CheckerManager &mgr) { \ |
626 | mgr.registerChecker<CHECKERNAME>(); \ |
627 | } \ |
628 | \ |
629 | bool ento::shouldRegister##CHECKERNAME(const CheckerManager &mgr) { \ |
630 | return true; \ |
631 | } |
632 | |
633 | REGISTER_CHECKER(UnixAPIMisuseChecker) |
634 | REGISTER_CHECKER(UnixAPIPortabilityChecker) |
635 | |