xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/ErrnoChecker.cpp (revision b1879975794772ee51f0b4865753364c7d7626c3)
1 //=== ErrnoChecker.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 defines an "errno checker" that can detect some invalid use of the
10 // system-defined value 'errno'. This checker works together with the
11 // ErrnoModeling checker and other checkers like StdCLibraryFunctions.
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #include "ErrnoModeling.h"
16 #include "clang/AST/ParentMapContext.h"
17 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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/ProgramState.h"
23 #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
24 #include "llvm/ADT/STLExtras.h"
25 #include <optional>
26 
27 using namespace clang;
28 using namespace ento;
29 using namespace errno_modeling;
30 
31 namespace {
32 
33 class ErrnoChecker
34     : public Checker<check::Location, check::PreCall, check::RegionChanges> {
35 public:
36   void checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
37                      CheckerContext &) const;
38   void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
39   ProgramStateRef
40   checkRegionChanges(ProgramStateRef State,
41                      const InvalidatedSymbols *Invalidated,
42                      ArrayRef<const MemRegion *> ExplicitRegions,
43                      ArrayRef<const MemRegion *> Regions,
44                      const LocationContext *LCtx, const CallEvent *Call) const;
45   void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const;
46 
47   /// Indicates if a read (load) of \c errno is allowed in a non-condition part
48   /// of \c if, \c switch, loop and conditional statements when the errno
49   /// value may be undefined.
50   bool AllowErrnoReadOutsideConditions = true;
51 
52 private:
53   void generateErrnoNotCheckedBug(CheckerContext &C, ProgramStateRef State,
54                                   const MemRegion *ErrnoRegion,
55                                   const CallEvent *CallMayChangeErrno) const;
56 
57   BugType BT_InvalidErrnoRead{this, "Value of 'errno' could be undefined",
58                               "Error handling"};
59   BugType BT_ErrnoNotChecked{this, "Value of 'errno' was not checked",
60                              "Error handling"};
61 };
62 
63 } // namespace
64 
65 static ProgramStateRef setErrnoStateIrrelevant(ProgramStateRef State) {
66   return setErrnoState(State, Irrelevant);
67 }
68 
69 /// Check if a statement (expression) or an ancestor of it is in a condition
70 /// part of a (conditional, loop, switch) statement.
71 static bool isInCondition(const Stmt *S, CheckerContext &C) {
72   ParentMapContext &ParentCtx = C.getASTContext().getParentMapContext();
73   bool CondFound = false;
74   while (S && !CondFound) {
75     const DynTypedNodeList Parents = ParentCtx.getParents(*S);
76     if (Parents.empty())
77       break;
78     const auto *ParentS = Parents[0].get<Stmt>();
79     if (!ParentS || isa<CallExpr>(ParentS))
80       break;
81     switch (ParentS->getStmtClass()) {
82     case Expr::IfStmtClass:
83       CondFound = (S == cast<IfStmt>(ParentS)->getCond());
84       break;
85     case Expr::ForStmtClass:
86       CondFound = (S == cast<ForStmt>(ParentS)->getCond());
87       break;
88     case Expr::DoStmtClass:
89       CondFound = (S == cast<DoStmt>(ParentS)->getCond());
90       break;
91     case Expr::WhileStmtClass:
92       CondFound = (S == cast<WhileStmt>(ParentS)->getCond());
93       break;
94     case Expr::SwitchStmtClass:
95       CondFound = (S == cast<SwitchStmt>(ParentS)->getCond());
96       break;
97     case Expr::ConditionalOperatorClass:
98       CondFound = (S == cast<ConditionalOperator>(ParentS)->getCond());
99       break;
100     case Expr::BinaryConditionalOperatorClass:
101       CondFound = (S == cast<BinaryConditionalOperator>(ParentS)->getCommon());
102       break;
103     default:
104       break;
105     }
106     S = ParentS;
107   }
108   return CondFound;
109 }
110 
111 void ErrnoChecker::generateErrnoNotCheckedBug(
112     CheckerContext &C, ProgramStateRef State, const MemRegion *ErrnoRegion,
113     const CallEvent *CallMayChangeErrno) const {
114   if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
115     SmallString<100> StrBuf;
116     llvm::raw_svector_ostream OS(StrBuf);
117     if (CallMayChangeErrno) {
118       OS << "Value of 'errno' was not checked and may be overwritten by "
119             "function '";
120       const auto *CallD =
121           dyn_cast_or_null<FunctionDecl>(CallMayChangeErrno->getDecl());
122       assert(CallD && CallD->getIdentifier());
123       OS << CallD->getIdentifier()->getName() << "'";
124     } else {
125       OS << "Value of 'errno' was not checked and is overwritten here";
126     }
127     auto BR = std::make_unique<PathSensitiveBugReport>(BT_ErrnoNotChecked,
128                                                        OS.str(), N);
129     BR->markInteresting(ErrnoRegion);
130     C.emitReport(std::move(BR));
131   }
132 }
133 
134 void ErrnoChecker::checkLocation(SVal Loc, bool IsLoad, const Stmt *S,
135                                  CheckerContext &C) const {
136   std::optional<ento::Loc> ErrnoLoc = getErrnoLoc(C.getState());
137   if (!ErrnoLoc)
138     return;
139 
140   auto L = Loc.getAs<ento::Loc>();
141   if (!L || *ErrnoLoc != *L)
142     return;
143 
144   ProgramStateRef State = C.getState();
145   ErrnoCheckState EState = getErrnoState(State);
146 
147   if (IsLoad) {
148     switch (EState) {
149     case MustNotBeChecked:
150       // Read of 'errno' when it may have undefined value.
151       if (!AllowErrnoReadOutsideConditions || isInCondition(S, C)) {
152         if (ExplodedNode *N = C.generateErrorNode()) {
153           auto BR = std::make_unique<PathSensitiveBugReport>(
154               BT_InvalidErrnoRead,
155               "An undefined value may be read from 'errno'", N);
156           BR->markInteresting(ErrnoLoc->getAsRegion());
157           C.emitReport(std::move(BR));
158         }
159       }
160       break;
161     case MustBeChecked:
162       // 'errno' has to be checked. A load is required for this, with no more
163       // information we can assume that it is checked somehow.
164       // After this place 'errno' is allowed to be read and written.
165       State = setErrnoStateIrrelevant(State);
166       C.addTransition(State);
167       break;
168     default:
169       break;
170     }
171   } else {
172     switch (EState) {
173     case MustBeChecked:
174       // 'errno' is overwritten without a read before but it should have been
175       // checked.
176       generateErrnoNotCheckedBug(C, setErrnoStateIrrelevant(State),
177                                  ErrnoLoc->getAsRegion(), nullptr);
178       break;
179     case MustNotBeChecked:
180       // Write to 'errno' when it is not allowed to be read.
181       // After this place 'errno' is allowed to be read and written.
182       State = setErrnoStateIrrelevant(State);
183       C.addTransition(State);
184       break;
185     default:
186       break;
187     }
188   }
189 }
190 
191 void ErrnoChecker::checkPreCall(const CallEvent &Call,
192                                 CheckerContext &C) const {
193   const auto *CallF = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
194   if (!CallF)
195     return;
196 
197   CallF = CallF->getCanonicalDecl();
198   // If 'errno' must be checked, it should be done as soon as possible, and
199   // before any other call to a system function (something in a system header).
200   // To avoid use of a long list of functions that may change 'errno'
201   // (which may be different with standard library versions) assume that any
202   // function can change it.
203   // A list of special functions can be used that are allowed here without
204   // generation of diagnostic. For now the only such case is 'errno' itself.
205   // Probably 'strerror'?
206   if (CallF->isExternC() && CallF->isGlobal() &&
207       C.getSourceManager().isInSystemHeader(CallF->getLocation()) &&
208       !isErrnoLocationCall(Call)) {
209     if (getErrnoState(C.getState()) == MustBeChecked) {
210       std::optional<ento::Loc> ErrnoLoc = getErrnoLoc(C.getState());
211       assert(ErrnoLoc && "ErrnoLoc should exist if an errno state is set.");
212       generateErrnoNotCheckedBug(C, setErrnoStateIrrelevant(C.getState()),
213                                  ErrnoLoc->getAsRegion(), &Call);
214     }
215   }
216 }
217 
218 ProgramStateRef ErrnoChecker::checkRegionChanges(
219     ProgramStateRef State, const InvalidatedSymbols *Invalidated,
220     ArrayRef<const MemRegion *> ExplicitRegions,
221     ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
222     const CallEvent *Call) const {
223   std::optional<ento::Loc> ErrnoLoc = getErrnoLoc(State);
224   if (!ErrnoLoc)
225     return State;
226   const MemRegion *ErrnoRegion = ErrnoLoc->getAsRegion();
227 
228   // If 'errno' is invalidated we can not know if it is checked or written into,
229   // allow read and write without bug reports.
230   if (llvm::is_contained(Regions, ErrnoRegion))
231     return clearErrnoState(State);
232 
233   // Always reset errno state when the system memory space is invalidated.
234   // The ErrnoRegion is not always found in the list in this case.
235   if (llvm::is_contained(Regions, ErrnoRegion->getMemorySpace()))
236     return clearErrnoState(State);
237 
238   return State;
239 }
240 
241 void ento::registerErrnoChecker(CheckerManager &mgr) {
242   const AnalyzerOptions &Opts = mgr.getAnalyzerOptions();
243   auto *Checker = mgr.registerChecker<ErrnoChecker>();
244   Checker->AllowErrnoReadOutsideConditions = Opts.getCheckerBooleanOption(
245       Checker, "AllowErrnoReadOutsideConditionExpressions");
246 }
247 
248 bool ento::shouldRegisterErrnoChecker(const CheckerManager &mgr) {
249   return true;
250 }
251