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