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/CallDescription.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
27using namespace clang;
28using namespace ento;
29using namespace errno_modeling;
30
31namespace {
32
33class ErrnoChecker
34    : public Checker<check::Location, check::PreCall, check::RegionChanges> {
35public:
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
52private:
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
65static 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.
71static 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
111void 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
134void 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
191void 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      !isErrno(CallF)) {
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
218ProgramStateRef 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
241void 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
248bool ento::shouldRegisterErrnoChecker(const CheckerManager &mgr) {
249  return true;
250}
251