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