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