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
setErrnoStateIrrelevant(ProgramStateRef State)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.
isInCondition(const Stmt * S,CheckerContext & C)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
generateErrnoNotCheckedBug(CheckerContext & C,ProgramStateRef State,const MemRegion * ErrnoRegion,const CallEvent * CallMayChangeErrno) const111 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
checkLocation(SVal Loc,bool IsLoad,const Stmt * S,CheckerContext & C) const134 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
checkPreCall(const CallEvent & Call,CheckerContext & C) const191 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
checkRegionChanges(ProgramStateRef State,const InvalidatedSymbols * Invalidated,ArrayRef<const MemRegion * > ExplicitRegions,ArrayRef<const MemRegion * > Regions,const LocationContext * LCtx,const CallEvent * Call) const218 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
registerErrnoChecker(CheckerManager & mgr)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
shouldRegisterErrnoChecker(const CheckerManager & mgr)248 bool ento::shouldRegisterErrnoChecker(const CheckerManager &mgr) {
249 return true;
250 }
251