1 //===-- StreamChecker.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 file defines checkers that model and check stream handling functions. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 14 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 15 #include "clang/StaticAnalyzer/Core/Checker.h" 16 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 17 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 18 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 19 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 20 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 21 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" 22 #include <functional> 23 24 using namespace clang; 25 using namespace ento; 26 using namespace std::placeholders; 27 28 namespace { 29 30 struct StreamState { 31 enum Kind { Opened, Closed, OpenFailed, Escaped } K; 32 33 StreamState(Kind k) : K(k) {} 34 35 bool isOpened() const { return K == Opened; } 36 bool isClosed() const { return K == Closed; } 37 //bool isOpenFailed() const { return K == OpenFailed; } 38 //bool isEscaped() const { return K == Escaped; } 39 40 bool operator==(const StreamState &X) const { return K == X.K; } 41 42 static StreamState getOpened() { return StreamState(Opened); } 43 static StreamState getClosed() { return StreamState(Closed); } 44 static StreamState getOpenFailed() { return StreamState(OpenFailed); } 45 static StreamState getEscaped() { return StreamState(Escaped); } 46 47 void Profile(llvm::FoldingSetNodeID &ID) const { 48 ID.AddInteger(K); 49 } 50 }; 51 52 class StreamChecker : public Checker<eval::Call, 53 check::DeadSymbols > { 54 mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence, 55 BT_doubleclose, BT_ResourceLeak; 56 57 public: 58 bool evalCall(const CallEvent &Call, CheckerContext &C) const; 59 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 60 61 private: 62 using FnCheck = std::function<void(const StreamChecker *, const CallEvent &, 63 CheckerContext &)>; 64 65 CallDescriptionMap<FnCheck> Callbacks = { 66 {{"fopen"}, &StreamChecker::evalFopen}, 67 {{"tmpfile"}, &StreamChecker::evalFopen}, 68 {{"fclose", 1}, &StreamChecker::evalFclose}, 69 {{"fread", 4}, 70 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}, 71 {{"fwrite", 4}, 72 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}, 73 {{"fseek", 3}, &StreamChecker::evalFseek}, 74 {{"ftell", 1}, 75 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 76 {{"rewind", 1}, 77 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 78 {{"fgetpos", 2}, 79 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 80 {{"fsetpos", 2}, 81 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 82 {{"clearerr", 1}, 83 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 84 {{"feof", 1}, 85 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 86 {{"ferror", 1}, 87 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 88 {{"fileno", 1}, 89 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 90 }; 91 92 void evalFopen(const CallEvent &Call, CheckerContext &C) const; 93 void evalFclose(const CallEvent &Call, CheckerContext &C) const; 94 void evalFseek(const CallEvent &Call, CheckerContext &C) const; 95 96 void checkArgNullStream(const CallEvent &Call, CheckerContext &C, 97 unsigned ArgI) const; 98 bool checkNullStream(SVal SV, CheckerContext &C, 99 ProgramStateRef &State) const; 100 void checkFseekWhence(SVal SV, CheckerContext &C, 101 ProgramStateRef &State) const; 102 bool checkDoubleClose(const CallEvent &Call, CheckerContext &C, 103 ProgramStateRef &State) const; 104 }; 105 106 } // end anonymous namespace 107 108 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 109 110 111 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { 112 const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); 113 if (!FD || FD->getKind() != Decl::Function) 114 return false; 115 116 // Recognize "global C functions" with only integral or pointer arguments 117 // (and matching name) as stream functions. 118 if (!Call.isGlobalCFunction()) 119 return false; 120 for (auto P : Call.parameters()) { 121 QualType T = P->getType(); 122 if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) 123 return false; 124 } 125 126 const FnCheck *Callback = Callbacks.lookup(Call); 127 if (!Callback) 128 return false; 129 130 (*Callback)(this, Call, C); 131 132 return C.isDifferent(); 133 } 134 135 void StreamChecker::evalFopen(const CallEvent &Call, CheckerContext &C) const { 136 ProgramStateRef state = C.getState(); 137 SValBuilder &svalBuilder = C.getSValBuilder(); 138 const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); 139 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 140 if (!CE) 141 return; 142 143 DefinedSVal RetVal = 144 svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) 145 .castAs<DefinedSVal>(); 146 state = state->BindExpr(CE, C.getLocationContext(), RetVal); 147 148 ConstraintManager &CM = C.getConstraintManager(); 149 // Bifurcate the state into two: one with a valid FILE* pointer, the other 150 // with a NULL. 151 ProgramStateRef stateNotNull, stateNull; 152 std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); 153 154 SymbolRef Sym = RetVal.getAsSymbol(); 155 assert(Sym && "RetVal must be a symbol here."); 156 stateNotNull = stateNotNull->set<StreamMap>(Sym, StreamState::getOpened()); 157 stateNull = stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed()); 158 159 C.addTransition(stateNotNull); 160 C.addTransition(stateNull); 161 } 162 163 void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const { 164 ProgramStateRef State = C.getState(); 165 if (checkDoubleClose(Call, C, State)) 166 C.addTransition(State); 167 } 168 169 void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const { 170 const Expr *AE2 = Call.getArgExpr(2); 171 if (!AE2) 172 return; 173 174 ProgramStateRef State = C.getState(); 175 176 bool StateChanged = checkNullStream(Call.getArgSVal(0), C, State); 177 // Check if error was generated. 178 if (C.isDifferent()) 179 return; 180 181 // Check the legality of the 'whence' argument of 'fseek'. 182 checkFseekWhence(State->getSVal(AE2, C.getLocationContext()), C, State); 183 184 if (!C.isDifferent() && StateChanged) 185 C.addTransition(State); 186 187 return; 188 } 189 190 void StreamChecker::checkArgNullStream(const CallEvent &Call, CheckerContext &C, 191 unsigned ArgI) const { 192 ProgramStateRef State = C.getState(); 193 if (checkNullStream(Call.getArgSVal(ArgI), C, State)) 194 C.addTransition(State); 195 } 196 197 bool StreamChecker::checkNullStream(SVal SV, CheckerContext &C, 198 ProgramStateRef &State) const { 199 Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>(); 200 if (!DV) 201 return false; 202 203 ConstraintManager &CM = C.getConstraintManager(); 204 ProgramStateRef StateNotNull, StateNull; 205 std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *DV); 206 207 if (!StateNotNull && StateNull) { 208 if (ExplodedNode *N = C.generateErrorNode(StateNull)) { 209 if (!BT_nullfp) 210 BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", 211 "Stream pointer might be NULL.")); 212 C.emitReport(std::make_unique<PathSensitiveBugReport>( 213 *BT_nullfp, BT_nullfp->getDescription(), N)); 214 } 215 return false; 216 } 217 218 if (StateNotNull) { 219 State = StateNotNull; 220 return true; 221 } 222 223 return false; 224 } 225 226 void StreamChecker::checkFseekWhence(SVal SV, CheckerContext &C, 227 ProgramStateRef &State) const { 228 Optional<nonloc::ConcreteInt> CI = SV.getAs<nonloc::ConcreteInt>(); 229 if (!CI) 230 return; 231 232 int64_t X = CI->getValue().getSExtValue(); 233 if (X >= 0 && X <= 2) 234 return; 235 236 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 237 if (!BT_illegalwhence) 238 BT_illegalwhence.reset( 239 new BuiltinBug(this, "Illegal whence argument", 240 "The whence argument to fseek() should be " 241 "SEEK_SET, SEEK_END, or SEEK_CUR.")); 242 C.emitReport(std::make_unique<PathSensitiveBugReport>( 243 *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); 244 } 245 } 246 247 bool StreamChecker::checkDoubleClose(const CallEvent &Call, CheckerContext &C, 248 ProgramStateRef &State) const { 249 SymbolRef Sym = Call.getArgSVal(0).getAsSymbol(); 250 if (!Sym) 251 return false; 252 253 const StreamState *SS = State->get<StreamMap>(Sym); 254 255 // If the file stream is not tracked, return. 256 if (!SS) 257 return false; 258 259 // Check: Double close a File Descriptor could cause undefined behaviour. 260 // Conforming to man-pages 261 if (SS->isClosed()) { 262 ExplodedNode *N = C.generateErrorNode(); 263 if (N) { 264 if (!BT_doubleclose) 265 BT_doubleclose.reset(new BuiltinBug( 266 this, "Double fclose", "Try to close a file Descriptor already" 267 " closed. Cause undefined behaviour.")); 268 C.emitReport(std::make_unique<PathSensitiveBugReport>( 269 *BT_doubleclose, BT_doubleclose->getDescription(), N)); 270 } 271 return false; 272 } 273 274 // Close the File Descriptor. 275 State = State->set<StreamMap>(Sym, StreamState::getClosed()); 276 277 return true; 278 } 279 280 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 281 CheckerContext &C) const { 282 ProgramStateRef State = C.getState(); 283 284 // TODO: Clean up the state. 285 const StreamMapTy &Map = State->get<StreamMap>(); 286 for (const auto &I: Map) { 287 SymbolRef Sym = I.first; 288 const StreamState &SS = I.second; 289 if (!SymReaper.isDead(Sym) || !SS.isOpened()) 290 continue; 291 292 ExplodedNode *N = C.generateErrorNode(); 293 if (!N) 294 continue; 295 296 if (!BT_ResourceLeak) 297 BT_ResourceLeak.reset( 298 new BuiltinBug(this, "Resource Leak", 299 "Opened File never closed. Potential Resource leak.")); 300 C.emitReport(std::make_unique<PathSensitiveBugReport>( 301 *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); 302 } 303 } 304 305 void ento::registerStreamChecker(CheckerManager &mgr) { 306 mgr.registerChecker<StreamChecker>(); 307 } 308 309 bool ento::shouldRegisterStreamChecker(const LangOptions &LO) { 310 return true; 311 } 312