1 1.1 joerg //===-- SimpleStreamChecker.cpp -----------------------------------------*- C++ -*--// 2 1.1 joerg // 3 1.1 joerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 1.1 joerg // See https://llvm.org/LICENSE.txt for license information. 5 1.1 joerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 1.1 joerg // 7 1.1 joerg //===----------------------------------------------------------------------===// 8 1.1 joerg // 9 1.1 joerg // Defines a checker for proper use of fopen/fclose APIs. 10 1.1 joerg // - If a file has been closed with fclose, it should not be accessed again. 11 1.1 joerg // Accessing a closed file results in undefined behavior. 12 1.1 joerg // - If a file was opened with fopen, it must be closed with fclose before 13 1.1 joerg // the execution ends. Failing to do so results in a resource leak. 14 1.1 joerg // 15 1.1 joerg //===----------------------------------------------------------------------===// 16 1.1 joerg 17 1.1 joerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 18 1.1 joerg #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 19 1.1 joerg #include "clang/StaticAnalyzer/Core/Checker.h" 20 1.1 joerg #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 21 1.1 joerg #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 22 1.1 joerg #include <utility> 23 1.1 joerg 24 1.1 joerg using namespace clang; 25 1.1 joerg using namespace ento; 26 1.1 joerg 27 1.1 joerg namespace { 28 1.1 joerg typedef SmallVector<SymbolRef, 2> SymbolVector; 29 1.1 joerg 30 1.1 joerg struct StreamState { 31 1.1 joerg private: 32 1.1 joerg enum Kind { Opened, Closed } K; 33 1.1 joerg StreamState(Kind InK) : K(InK) { } 34 1.1 joerg 35 1.1 joerg public: 36 1.1 joerg bool isOpened() const { return K == Opened; } 37 1.1 joerg bool isClosed() const { return K == Closed; } 38 1.1 joerg 39 1.1 joerg static StreamState getOpened() { return StreamState(Opened); } 40 1.1 joerg static StreamState getClosed() { return StreamState(Closed); } 41 1.1 joerg 42 1.1 joerg bool operator==(const StreamState &X) const { 43 1.1 joerg return K == X.K; 44 1.1 joerg } 45 1.1 joerg void Profile(llvm::FoldingSetNodeID &ID) const { 46 1.1 joerg ID.AddInteger(K); 47 1.1 joerg } 48 1.1 joerg }; 49 1.1 joerg 50 1.1 joerg class SimpleStreamChecker : public Checker<check::PostCall, 51 1.1 joerg check::PreCall, 52 1.1 joerg check::DeadSymbols, 53 1.1 joerg check::PointerEscape> { 54 1.1 joerg CallDescription OpenFn, CloseFn; 55 1.1 joerg 56 1.1 joerg std::unique_ptr<BugType> DoubleCloseBugType; 57 1.1 joerg std::unique_ptr<BugType> LeakBugType; 58 1.1 joerg 59 1.1 joerg void reportDoubleClose(SymbolRef FileDescSym, 60 1.1 joerg const CallEvent &Call, 61 1.1 joerg CheckerContext &C) const; 62 1.1 joerg 63 1.1 joerg void reportLeaks(ArrayRef<SymbolRef> LeakedStreams, CheckerContext &C, 64 1.1 joerg ExplodedNode *ErrNode) const; 65 1.1 joerg 66 1.1 joerg bool guaranteedNotToCloseFile(const CallEvent &Call) const; 67 1.1 joerg 68 1.1 joerg public: 69 1.1 joerg SimpleStreamChecker(); 70 1.1 joerg 71 1.1 joerg /// Process fopen. 72 1.1 joerg void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 73 1.1 joerg /// Process fclose. 74 1.1 joerg void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 75 1.1 joerg 76 1.1 joerg void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 77 1.1 joerg 78 1.1 joerg /// Stop tracking addresses which escape. 79 1.1 joerg ProgramStateRef checkPointerEscape(ProgramStateRef State, 80 1.1 joerg const InvalidatedSymbols &Escaped, 81 1.1 joerg const CallEvent *Call, 82 1.1 joerg PointerEscapeKind Kind) const; 83 1.1 joerg }; 84 1.1 joerg 85 1.1 joerg } // end anonymous namespace 86 1.1 joerg 87 1.1 joerg /// The state of the checker is a map from tracked stream symbols to their 88 1.1 joerg /// state. Let's store it in the ProgramState. 89 1.1 joerg REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 90 1.1 joerg 91 1.1 joerg namespace { 92 1.1 joerg class StopTrackingCallback final : public SymbolVisitor { 93 1.1 joerg ProgramStateRef state; 94 1.1 joerg public: 95 1.1 joerg StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {} 96 1.1 joerg ProgramStateRef getState() const { return state; } 97 1.1 joerg 98 1.1 joerg bool VisitSymbol(SymbolRef sym) override { 99 1.1 joerg state = state->remove<StreamMap>(sym); 100 1.1 joerg return true; 101 1.1 joerg } 102 1.1 joerg }; 103 1.1 joerg } // end anonymous namespace 104 1.1 joerg 105 1.1 joerg SimpleStreamChecker::SimpleStreamChecker() 106 1.1 joerg : OpenFn("fopen"), CloseFn("fclose", 1) { 107 1.1 joerg // Initialize the bug types. 108 1.1 joerg DoubleCloseBugType.reset( 109 1.1 joerg new BugType(this, "Double fclose", "Unix Stream API Error")); 110 1.1 joerg 111 1.1 joerg // Sinks are higher importance bugs as well as calls to assert() or exit(0). 112 1.1 joerg LeakBugType.reset( 113 1.1 joerg new BugType(this, "Resource Leak", "Unix Stream API Error", 114 1.1 joerg /*SuppressOnSink=*/true)); 115 1.1 joerg } 116 1.1 joerg 117 1.1 joerg void SimpleStreamChecker::checkPostCall(const CallEvent &Call, 118 1.1 joerg CheckerContext &C) const { 119 1.1 joerg if (!Call.isGlobalCFunction()) 120 1.1 joerg return; 121 1.1 joerg 122 1.1 joerg if (!Call.isCalled(OpenFn)) 123 1.1 joerg return; 124 1.1 joerg 125 1.1 joerg // Get the symbolic value corresponding to the file handle. 126 1.1 joerg SymbolRef FileDesc = Call.getReturnValue().getAsSymbol(); 127 1.1 joerg if (!FileDesc) 128 1.1 joerg return; 129 1.1 joerg 130 1.1 joerg // Generate the next transition (an edge in the exploded graph). 131 1.1 joerg ProgramStateRef State = C.getState(); 132 1.1 joerg State = State->set<StreamMap>(FileDesc, StreamState::getOpened()); 133 1.1 joerg C.addTransition(State); 134 1.1 joerg } 135 1.1 joerg 136 1.1 joerg void SimpleStreamChecker::checkPreCall(const CallEvent &Call, 137 1.1 joerg CheckerContext &C) const { 138 1.1 joerg if (!Call.isGlobalCFunction()) 139 1.1 joerg return; 140 1.1 joerg 141 1.1 joerg if (!Call.isCalled(CloseFn)) 142 1.1 joerg return; 143 1.1 joerg 144 1.1 joerg // Get the symbolic value corresponding to the file handle. 145 1.1 joerg SymbolRef FileDesc = Call.getArgSVal(0).getAsSymbol(); 146 1.1 joerg if (!FileDesc) 147 1.1 joerg return; 148 1.1 joerg 149 1.1 joerg // Check if the stream has already been closed. 150 1.1 joerg ProgramStateRef State = C.getState(); 151 1.1 joerg const StreamState *SS = State->get<StreamMap>(FileDesc); 152 1.1 joerg if (SS && SS->isClosed()) { 153 1.1 joerg reportDoubleClose(FileDesc, Call, C); 154 1.1 joerg return; 155 1.1 joerg } 156 1.1 joerg 157 1.1 joerg // Generate the next transition, in which the stream is closed. 158 1.1 joerg State = State->set<StreamMap>(FileDesc, StreamState::getClosed()); 159 1.1 joerg C.addTransition(State); 160 1.1 joerg } 161 1.1 joerg 162 1.1 joerg static bool isLeaked(SymbolRef Sym, const StreamState &SS, 163 1.1 joerg bool IsSymDead, ProgramStateRef State) { 164 1.1 joerg if (IsSymDead && SS.isOpened()) { 165 1.1 joerg // If a symbol is NULL, assume that fopen failed on this path. 166 1.1 joerg // A symbol should only be considered leaked if it is non-null. 167 1.1 joerg ConstraintManager &CMgr = State->getConstraintManager(); 168 1.1 joerg ConditionTruthVal OpenFailed = CMgr.isNull(State, Sym); 169 1.1 joerg return !OpenFailed.isConstrainedTrue(); 170 1.1 joerg } 171 1.1 joerg return false; 172 1.1 joerg } 173 1.1 joerg 174 1.1 joerg void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 175 1.1 joerg CheckerContext &C) const { 176 1.1 joerg ProgramStateRef State = C.getState(); 177 1.1 joerg SymbolVector LeakedStreams; 178 1.1 joerg StreamMapTy TrackedStreams = State->get<StreamMap>(); 179 1.1 joerg for (StreamMapTy::iterator I = TrackedStreams.begin(), 180 1.1 joerg E = TrackedStreams.end(); I != E; ++I) { 181 1.1 joerg SymbolRef Sym = I->first; 182 1.1 joerg bool IsSymDead = SymReaper.isDead(Sym); 183 1.1 joerg 184 1.1 joerg // Collect leaked symbols. 185 1.1 joerg if (isLeaked(Sym, I->second, IsSymDead, State)) 186 1.1 joerg LeakedStreams.push_back(Sym); 187 1.1 joerg 188 1.1 joerg // Remove the dead symbol from the streams map. 189 1.1 joerg if (IsSymDead) 190 1.1 joerg State = State->remove<StreamMap>(Sym); 191 1.1 joerg } 192 1.1 joerg 193 1.1 joerg ExplodedNode *N = C.generateNonFatalErrorNode(State); 194 1.1 joerg if (!N) 195 1.1 joerg return; 196 1.1 joerg reportLeaks(LeakedStreams, C, N); 197 1.1 joerg } 198 1.1 joerg 199 1.1 joerg void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, 200 1.1 joerg const CallEvent &Call, 201 1.1 joerg CheckerContext &C) const { 202 1.1 joerg // We reached a bug, stop exploring the path here by generating a sink. 203 1.1 joerg ExplodedNode *ErrNode = C.generateErrorNode(); 204 1.1 joerg // If we've already reached this node on another path, return. 205 1.1 joerg if (!ErrNode) 206 1.1 joerg return; 207 1.1 joerg 208 1.1 joerg // Generate the report. 209 1.1 joerg auto R = std::make_unique<PathSensitiveBugReport>( 210 1.1 joerg *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode); 211 1.1 joerg R->addRange(Call.getSourceRange()); 212 1.1 joerg R->markInteresting(FileDescSym); 213 1.1 joerg C.emitReport(std::move(R)); 214 1.1 joerg } 215 1.1 joerg 216 1.1 joerg void SimpleStreamChecker::reportLeaks(ArrayRef<SymbolRef> LeakedStreams, 217 1.1 joerg CheckerContext &C, 218 1.1 joerg ExplodedNode *ErrNode) const { 219 1.1 joerg // Attach bug reports to the leak node. 220 1.1 joerg // TODO: Identify the leaked file descriptor. 221 1.1 joerg for (SymbolRef LeakedStream : LeakedStreams) { 222 1.1 joerg auto R = std::make_unique<PathSensitiveBugReport>( 223 1.1 joerg *LeakBugType, "Opened file is never closed; potential resource leak", 224 1.1 joerg ErrNode); 225 1.1 joerg R->markInteresting(LeakedStream); 226 1.1 joerg C.emitReport(std::move(R)); 227 1.1 joerg } 228 1.1 joerg } 229 1.1 joerg 230 1.1 joerg bool SimpleStreamChecker::guaranteedNotToCloseFile(const CallEvent &Call) const{ 231 1.1 joerg // If it's not in a system header, assume it might close a file. 232 1.1 joerg if (!Call.isInSystemHeader()) 233 1.1 joerg return false; 234 1.1 joerg 235 1.1 joerg // Handle cases where we know a buffer's /address/ can escape. 236 1.1 joerg if (Call.argumentsMayEscape()) 237 1.1 joerg return false; 238 1.1 joerg 239 1.1 joerg // Note, even though fclose closes the file, we do not list it here 240 1.1 joerg // since the checker is modeling the call. 241 1.1 joerg 242 1.1 joerg return true; 243 1.1 joerg } 244 1.1 joerg 245 1.1 joerg // If the pointer we are tracking escaped, do not track the symbol as 246 1.1 joerg // we cannot reason about it anymore. 247 1.1 joerg ProgramStateRef 248 1.1 joerg SimpleStreamChecker::checkPointerEscape(ProgramStateRef State, 249 1.1 joerg const InvalidatedSymbols &Escaped, 250 1.1 joerg const CallEvent *Call, 251 1.1 joerg PointerEscapeKind Kind) const { 252 1.1 joerg // If we know that the call cannot close a file, there is nothing to do. 253 1.1 joerg if (Kind == PSK_DirectEscapeOnCall && guaranteedNotToCloseFile(*Call)) { 254 1.1 joerg return State; 255 1.1 joerg } 256 1.1 joerg 257 1.1 joerg for (InvalidatedSymbols::const_iterator I = Escaped.begin(), 258 1.1 joerg E = Escaped.end(); 259 1.1 joerg I != E; ++I) { 260 1.1 joerg SymbolRef Sym = *I; 261 1.1 joerg 262 1.1 joerg // The symbol escaped. Optimistically, assume that the corresponding file 263 1.1 joerg // handle will be closed somewhere else. 264 1.1 joerg State = State->remove<StreamMap>(Sym); 265 1.1 joerg } 266 1.1 joerg return State; 267 1.1 joerg } 268 1.1 joerg 269 1.1 joerg void ento::registerSimpleStreamChecker(CheckerManager &mgr) { 270 1.1 joerg mgr.registerChecker<SimpleStreamChecker>(); 271 1.1 joerg } 272 1.1 joerg 273 1.1 joerg // This checker should be enabled regardless of how language options are set. 274 1.1.1.2 joerg bool ento::shouldRegisterSimpleStreamChecker(const CheckerManager &mgr) { 275 1.1 joerg return true; 276 1.1 joerg } 277