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 &svalBuilder = C.getSValBuilder(); 140 const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); 141 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 142 if (!CE) 143 return; 144 145 DefinedSVal RetVal = 146 svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) 147 .castAs<DefinedSVal>(); 148 state = state->BindExpr(CE, C.getLocationContext(), RetVal); 149 150 ConstraintManager &CM = C.getConstraintManager(); 151 // Bifurcate the state into two: one with a valid FILE* pointer, the other 152 // with a NULL. 153 ProgramStateRef stateNotNull, stateNull; 154 std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); 155 156 SymbolRef Sym = RetVal.getAsSymbol(); 157 assert(Sym && "RetVal must be a symbol here."); 158 stateNotNull = stateNotNull->set<StreamMap>(Sym, StreamState::getOpened()); 159 stateNull = stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed()); 160 161 C.addTransition(stateNotNull); 162 C.addTransition(stateNull); 163 } 164 165 void StreamChecker::evalFreopen(const CallEvent &Call, 166 CheckerContext &C) const { 167 ProgramStateRef State = C.getState(); 168 169 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 170 if (!CE) 171 return; 172 173 Optional<DefinedSVal> StreamVal = Call.getArgSVal(2).getAs<DefinedSVal>(); 174 if (!StreamVal) 175 return; 176 // Do not allow NULL as passed stream pointer. 177 // This is not specified in the man page but may crash on some system. 178 checkNullStream(*StreamVal, C, State); 179 // Check if error was generated. 180 if (C.isDifferent()) 181 return; 182 183 SymbolRef StreamSym = StreamVal->getAsSymbol(); 184 // Do not care about special values for stream ("(FILE *)0x12345"?). 185 if (!StreamSym) 186 return; 187 188 // Generate state for non-failed case. 189 // Return value is the passed stream pointer. 190 // According to the documentations, the stream is closed first 191 // but any close error is ignored. The state changes to (or remains) opened. 192 ProgramStateRef StateRetNotNull = 193 State->BindExpr(CE, C.getLocationContext(), *StreamVal); 194 // Generate state for NULL return value. 195 // Stream switches to OpenFailed state. 196 ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(), 197 C.getSValBuilder().makeNull()); 198 199 StateRetNotNull = 200 StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened()); 201 StateRetNull = 202 StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed()); 203 204 C.addTransition(StateRetNotNull); 205 C.addTransition(StateRetNull); 206 } 207 208 void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const { 209 ProgramStateRef State = C.getState(); 210 if (checkDoubleClose(Call, C, State)) 211 C.addTransition(State); 212 } 213 214 void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const { 215 const Expr *AE2 = Call.getArgExpr(2); 216 if (!AE2) 217 return; 218 219 ProgramStateRef State = C.getState(); 220 221 bool StateChanged = checkNullStream(Call.getArgSVal(0), C, State); 222 // Check if error was generated. 223 if (C.isDifferent()) 224 return; 225 226 // Check the legality of the 'whence' argument of 'fseek'. 227 checkFseekWhence(State->getSVal(AE2, C.getLocationContext()), C, State); 228 229 if (!C.isDifferent() && StateChanged) 230 C.addTransition(State); 231 232 return; 233 } 234 235 void StreamChecker::checkArgNullStream(const CallEvent &Call, CheckerContext &C, 236 unsigned ArgI) const { 237 ProgramStateRef State = C.getState(); 238 if (checkNullStream(Call.getArgSVal(ArgI), C, State)) 239 C.addTransition(State); 240 } 241 242 bool StreamChecker::checkNullStream(SVal SV, CheckerContext &C, 243 ProgramStateRef &State) const { 244 Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>(); 245 if (!DV) 246 return false; 247 248 ConstraintManager &CM = C.getConstraintManager(); 249 ProgramStateRef StateNotNull, StateNull; 250 std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *DV); 251 252 if (!StateNotNull && StateNull) { 253 if (ExplodedNode *N = C.generateErrorNode(StateNull)) { 254 if (!BT_nullfp) 255 BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", 256 "Stream pointer might be NULL.")); 257 C.emitReport(std::make_unique<PathSensitiveBugReport>( 258 *BT_nullfp, BT_nullfp->getDescription(), N)); 259 } 260 return false; 261 } 262 263 if (StateNotNull) { 264 State = StateNotNull; 265 return true; 266 } 267 268 return false; 269 } 270 271 void StreamChecker::checkFseekWhence(SVal SV, CheckerContext &C, 272 ProgramStateRef &State) const { 273 Optional<nonloc::ConcreteInt> CI = SV.getAs<nonloc::ConcreteInt>(); 274 if (!CI) 275 return; 276 277 int64_t X = CI->getValue().getSExtValue(); 278 if (X >= 0 && X <= 2) 279 return; 280 281 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 282 if (!BT_illegalwhence) 283 BT_illegalwhence.reset( 284 new BuiltinBug(this, "Illegal whence argument", 285 "The whence argument to fseek() should be " 286 "SEEK_SET, SEEK_END, or SEEK_CUR.")); 287 C.emitReport(std::make_unique<PathSensitiveBugReport>( 288 *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); 289 } 290 } 291 292 bool StreamChecker::checkDoubleClose(const CallEvent &Call, CheckerContext &C, 293 ProgramStateRef &State) const { 294 SymbolRef Sym = Call.getArgSVal(0).getAsSymbol(); 295 if (!Sym) 296 return false; 297 298 const StreamState *SS = State->get<StreamMap>(Sym); 299 300 // If the file stream is not tracked, return. 301 if (!SS) 302 return false; 303 304 // Check: Double close a File Descriptor could cause undefined behaviour. 305 // Conforming to man-pages 306 if (SS->isClosed()) { 307 ExplodedNode *N = C.generateErrorNode(); 308 if (N) { 309 if (!BT_doubleclose) 310 BT_doubleclose.reset(new BuiltinBug( 311 this, "Double fclose", "Try to close a file Descriptor already" 312 " closed. Cause undefined behaviour.")); 313 C.emitReport(std::make_unique<PathSensitiveBugReport>( 314 *BT_doubleclose, BT_doubleclose->getDescription(), N)); 315 } 316 return false; 317 } 318 319 // Close the File Descriptor. 320 State = State->set<StreamMap>(Sym, StreamState::getClosed()); 321 322 return true; 323 } 324 325 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 326 CheckerContext &C) const { 327 ProgramStateRef State = C.getState(); 328 329 // TODO: Clean up the state. 330 const StreamMapTy &Map = State->get<StreamMap>(); 331 for (const auto &I: Map) { 332 SymbolRef Sym = I.first; 333 const StreamState &SS = I.second; 334 if (!SymReaper.isDead(Sym) || !SS.isOpened()) 335 continue; 336 337 ExplodedNode *N = C.generateErrorNode(); 338 if (!N) 339 continue; 340 341 if (!BT_ResourceLeak) 342 BT_ResourceLeak.reset( 343 new BuiltinBug(this, "Resource Leak", 344 "Opened File never closed. Potential Resource leak.")); 345 C.emitReport(std::make_unique<PathSensitiveBugReport>( 346 *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); 347 } 348 } 349 350 void ento::registerStreamChecker(CheckerManager &mgr) { 351 mgr.registerChecker<StreamChecker>(); 352 } 353 354 bool ento::shouldRegisterStreamChecker(const LangOptions &LO) { 355 return true; 356 } 357