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 {{"freopen", 3}, &StreamChecker::evalFreopen}, 68 {{"tmpfile"}, &StreamChecker::evalFopen}, 69 {{"fclose", 1}, &StreamChecker::evalFclose}, 70 {{"fread", 4}, 71 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}, 72 {{"fwrite", 4}, 73 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}, 74 {{"fseek", 3}, &StreamChecker::evalFseek}, 75 {{"ftell", 1}, 76 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 77 {{"rewind", 1}, 78 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 79 {{"fgetpos", 2}, 80 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 81 {{"fsetpos", 2}, 82 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 83 {{"clearerr", 1}, 84 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 85 {{"feof", 1}, 86 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 87 {{"ferror", 1}, 88 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 89 {{"fileno", 1}, 90 std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, 91 }; 92 93 void evalFopen(const CallEvent &Call, CheckerContext &C) const; 94 void evalFreopen(const CallEvent &Call, CheckerContext &C) const; 95 void evalFclose(const CallEvent &Call, CheckerContext &C) const; 96 void evalFseek(const CallEvent &Call, CheckerContext &C) const; 97 98 void checkArgNullStream(const CallEvent &Call, CheckerContext &C, 99 unsigned ArgI) const; 100 bool checkNullStream(SVal SV, CheckerContext &C, 101 ProgramStateRef &State) const; 102 void checkFseekWhence(SVal SV, CheckerContext &C, 103 ProgramStateRef &State) const; 104 bool checkDoubleClose(const CallEvent &Call, CheckerContext &C, 105 ProgramStateRef &State) const; 106 }; 107 108 } // end anonymous namespace 109 110 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 111 112 113 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { 114 const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); 115 if (!FD || FD->getKind() != Decl::Function) 116 return false; 117 118 // Recognize "global C functions" with only integral or pointer arguments 119 // (and matching name) as stream functions. 120 if (!Call.isGlobalCFunction()) 121 return false; 122 for (auto P : Call.parameters()) { 123 QualType T = P->getType(); 124 if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) 125 return false; 126 } 127 128 const FnCheck *Callback = Callbacks.lookup(Call); 129 if (!Callback) 130 return false; 131 132 (*Callback)(this, Call, C); 133 134 return C.isDifferent(); 135 } 136 137 void StreamChecker::evalFopen(const CallEvent &Call, CheckerContext &C) const { 138 ProgramStateRef State = C.getState(); 139 SValBuilder &SVB = C.getSValBuilder(); 140 const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); 141 142 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 143 if (!CE) 144 return; 145 146 DefinedSVal RetVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) 147 .castAs<DefinedSVal>(); 148 SymbolRef RetSym = RetVal.getAsSymbol(); 149 assert(RetSym && "RetVal must be a symbol here."); 150 151 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 152 153 // Bifurcate the state into two: one with a valid FILE* pointer, the other 154 // with a NULL. 155 ProgramStateRef StateNotNull, StateNull; 156 std::tie(StateNotNull, StateNull) = 157 C.getConstraintManager().assumeDual(State, RetVal); 158 159 StateNotNull = StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened()); 160 StateNull = StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed()); 161 162 C.addTransition(StateNotNull); 163 C.addTransition(StateNull); 164 } 165 166 void StreamChecker::evalFreopen(const CallEvent &Call, 167 CheckerContext &C) const { 168 ProgramStateRef State = C.getState(); 169 170 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 171 if (!CE) 172 return; 173 174 Optional<DefinedSVal> StreamVal = Call.getArgSVal(2).getAs<DefinedSVal>(); 175 if (!StreamVal) 176 return; 177 // Do not allow NULL as passed stream pointer. 178 // This is not specified in the man page but may crash on some system. 179 checkNullStream(*StreamVal, C, State); 180 // Check if error was generated. 181 if (C.isDifferent()) 182 return; 183 184 SymbolRef StreamSym = StreamVal->getAsSymbol(); 185 // Do not care about special values for stream ("(FILE *)0x12345"?). 186 if (!StreamSym) 187 return; 188 189 // Generate state for non-failed case. 190 // Return value is the passed stream pointer. 191 // According to the documentations, the stream is closed first 192 // but any close error is ignored. The state changes to (or remains) opened. 193 ProgramStateRef StateRetNotNull = 194 State->BindExpr(CE, C.getLocationContext(), *StreamVal); 195 // Generate state for NULL return value. 196 // Stream switches to OpenFailed state. 197 ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(), 198 C.getSValBuilder().makeNull()); 199 200 StateRetNotNull = 201 StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened()); 202 StateRetNull = 203 StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed()); 204 205 C.addTransition(StateRetNotNull); 206 C.addTransition(StateRetNull); 207 } 208 209 void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const { 210 ProgramStateRef State = C.getState(); 211 if (checkDoubleClose(Call, C, State)) 212 C.addTransition(State); 213 } 214 215 void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const { 216 const Expr *AE2 = Call.getArgExpr(2); 217 if (!AE2) 218 return; 219 220 ProgramStateRef State = C.getState(); 221 222 bool StateChanged = checkNullStream(Call.getArgSVal(0), C, State); 223 // Check if error was generated. 224 if (C.isDifferent()) 225 return; 226 227 // Check the legality of the 'whence' argument of 'fseek'. 228 checkFseekWhence(State->getSVal(AE2, C.getLocationContext()), C, State); 229 230 if (!C.isDifferent() && StateChanged) 231 C.addTransition(State); 232 } 233 234 void StreamChecker::checkArgNullStream(const CallEvent &Call, CheckerContext &C, 235 unsigned ArgI) const { 236 ProgramStateRef State = C.getState(); 237 if (checkNullStream(Call.getArgSVal(ArgI), C, State)) 238 C.addTransition(State); 239 } 240 241 bool StreamChecker::checkNullStream(SVal SV, CheckerContext &C, 242 ProgramStateRef &State) const { 243 Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>(); 244 if (!DV) 245 return false; 246 247 ConstraintManager &CM = C.getConstraintManager(); 248 ProgramStateRef StateNotNull, StateNull; 249 std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *DV); 250 251 if (!StateNotNull && StateNull) { 252 if (ExplodedNode *N = C.generateErrorNode(StateNull)) { 253 if (!BT_nullfp) 254 BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", 255 "Stream pointer might be NULL.")); 256 C.emitReport(std::make_unique<PathSensitiveBugReport>( 257 *BT_nullfp, BT_nullfp->getDescription(), N)); 258 } 259 return false; 260 } 261 262 if (StateNotNull) { 263 State = StateNotNull; 264 return true; 265 } 266 267 return false; 268 } 269 270 void StreamChecker::checkFseekWhence(SVal SV, CheckerContext &C, 271 ProgramStateRef &State) const { 272 Optional<nonloc::ConcreteInt> CI = SV.getAs<nonloc::ConcreteInt>(); 273 if (!CI) 274 return; 275 276 int64_t X = CI->getValue().getSExtValue(); 277 if (X >= 0 && X <= 2) 278 return; 279 280 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 281 if (!BT_illegalwhence) 282 BT_illegalwhence.reset( 283 new BuiltinBug(this, "Illegal whence argument", 284 "The whence argument to fseek() should be " 285 "SEEK_SET, SEEK_END, or SEEK_CUR.")); 286 C.emitReport(std::make_unique<PathSensitiveBugReport>( 287 *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); 288 } 289 } 290 291 bool StreamChecker::checkDoubleClose(const CallEvent &Call, CheckerContext &C, 292 ProgramStateRef &State) const { 293 SymbolRef Sym = Call.getArgSVal(0).getAsSymbol(); 294 if (!Sym) 295 return false; 296 297 const StreamState *SS = State->get<StreamMap>(Sym); 298 299 // If the file stream is not tracked, return. 300 if (!SS) 301 return false; 302 303 // Check: Double close a File Descriptor could cause undefined behaviour. 304 // Conforming to man-pages 305 if (SS->isClosed()) { 306 ExplodedNode *N = C.generateErrorNode(); 307 if (N) { 308 if (!BT_doubleclose) 309 BT_doubleclose.reset(new BuiltinBug( 310 this, "Double fclose", "Try to close a file Descriptor already" 311 " closed. Cause undefined behaviour.")); 312 C.emitReport(std::make_unique<PathSensitiveBugReport>( 313 *BT_doubleclose, BT_doubleclose->getDescription(), N)); 314 } 315 return false; 316 } 317 318 // Close the File Descriptor. 319 State = State->set<StreamMap>(Sym, StreamState::getClosed()); 320 321 return true; 322 } 323 324 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 325 CheckerContext &C) const { 326 ProgramStateRef State = C.getState(); 327 328 // TODO: Clean up the state. 329 const StreamMapTy &Map = State->get<StreamMap>(); 330 for (const auto &I: Map) { 331 SymbolRef Sym = I.first; 332 const StreamState &SS = I.second; 333 if (!SymReaper.isDead(Sym) || !SS.isOpened()) 334 continue; 335 336 ExplodedNode *N = C.generateErrorNode(); 337 if (!N) 338 continue; 339 340 if (!BT_ResourceLeak) 341 BT_ResourceLeak.reset( 342 new BuiltinBug(this, "Resource Leak", 343 "Opened File never closed. Potential Resource leak.")); 344 C.emitReport(std::make_unique<PathSensitiveBugReport>( 345 *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); 346 } 347 } 348 349 void ento::registerStreamChecker(CheckerManager &mgr) { 350 mgr.registerChecker<StreamChecker>(); 351 } 352 353 bool ento::shouldRegisterStreamChecker(const LangOptions &LO) { 354 return true; 355 } 356