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 23 using namespace clang; 24 using namespace ento; 25 26 namespace { 27 28 struct StreamState { 29 enum Kind { Opened, Closed, OpenFailed, Escaped } K; 30 const Stmt *S; 31 32 StreamState(Kind k, const Stmt *s) : K(k), S(s) {} 33 34 bool isOpened() const { return K == Opened; } 35 bool isClosed() const { return K == Closed; } 36 //bool isOpenFailed() const { return K == OpenFailed; } 37 //bool isEscaped() const { return K == Escaped; } 38 39 bool operator==(const StreamState &X) const { 40 return K == X.K && S == X.S; 41 } 42 43 static StreamState getOpened(const Stmt *s) { return StreamState(Opened, s); } 44 static StreamState getClosed(const Stmt *s) { return StreamState(Closed, s); } 45 static StreamState getOpenFailed(const Stmt *s) { 46 return StreamState(OpenFailed, s); 47 } 48 static StreamState getEscaped(const Stmt *s) { 49 return StreamState(Escaped, s); 50 } 51 52 void Profile(llvm::FoldingSetNodeID &ID) const { 53 ID.AddInteger(K); 54 ID.AddPointer(S); 55 } 56 }; 57 58 class StreamChecker : public Checker<eval::Call, 59 check::DeadSymbols > { 60 mutable IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread, 61 *II_fwrite, 62 *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos, 63 *II_clearerr, *II_feof, *II_ferror, *II_fileno; 64 mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence, 65 BT_doubleclose, BT_ResourceLeak; 66 67 public: 68 StreamChecker() 69 : II_fopen(nullptr), II_tmpfile(nullptr), II_fclose(nullptr), 70 II_fread(nullptr), II_fwrite(nullptr), II_fseek(nullptr), 71 II_ftell(nullptr), II_rewind(nullptr), II_fgetpos(nullptr), 72 II_fsetpos(nullptr), II_clearerr(nullptr), II_feof(nullptr), 73 II_ferror(nullptr), II_fileno(nullptr) {} 74 75 bool evalCall(const CallEvent &Call, CheckerContext &C) const; 76 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 77 78 private: 79 void Fopen(CheckerContext &C, const CallExpr *CE) const; 80 void Tmpfile(CheckerContext &C, const CallExpr *CE) const; 81 void Fclose(CheckerContext &C, const CallExpr *CE) const; 82 void Fread(CheckerContext &C, const CallExpr *CE) const; 83 void Fwrite(CheckerContext &C, const CallExpr *CE) const; 84 void Fseek(CheckerContext &C, const CallExpr *CE) const; 85 void Ftell(CheckerContext &C, const CallExpr *CE) const; 86 void Rewind(CheckerContext &C, const CallExpr *CE) const; 87 void Fgetpos(CheckerContext &C, const CallExpr *CE) const; 88 void Fsetpos(CheckerContext &C, const CallExpr *CE) const; 89 void Clearerr(CheckerContext &C, const CallExpr *CE) const; 90 void Feof(CheckerContext &C, const CallExpr *CE) const; 91 void Ferror(CheckerContext &C, const CallExpr *CE) const; 92 void Fileno(CheckerContext &C, const CallExpr *CE) const; 93 94 void OpenFileAux(CheckerContext &C, const CallExpr *CE) const; 95 96 ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state, 97 CheckerContext &C) const; 98 ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state, 99 CheckerContext &C) const; 100 }; 101 102 } // end anonymous namespace 103 104 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 105 106 107 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { 108 const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); 109 if (!FD || FD->getKind() != Decl::Function) 110 return false; 111 112 const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 113 if (!CE) 114 return false; 115 116 ASTContext &Ctx = C.getASTContext(); 117 if (!II_fopen) 118 II_fopen = &Ctx.Idents.get("fopen"); 119 if (!II_tmpfile) 120 II_tmpfile = &Ctx.Idents.get("tmpfile"); 121 if (!II_fclose) 122 II_fclose = &Ctx.Idents.get("fclose"); 123 if (!II_fread) 124 II_fread = &Ctx.Idents.get("fread"); 125 if (!II_fwrite) 126 II_fwrite = &Ctx.Idents.get("fwrite"); 127 if (!II_fseek) 128 II_fseek = &Ctx.Idents.get("fseek"); 129 if (!II_ftell) 130 II_ftell = &Ctx.Idents.get("ftell"); 131 if (!II_rewind) 132 II_rewind = &Ctx.Idents.get("rewind"); 133 if (!II_fgetpos) 134 II_fgetpos = &Ctx.Idents.get("fgetpos"); 135 if (!II_fsetpos) 136 II_fsetpos = &Ctx.Idents.get("fsetpos"); 137 if (!II_clearerr) 138 II_clearerr = &Ctx.Idents.get("clearerr"); 139 if (!II_feof) 140 II_feof = &Ctx.Idents.get("feof"); 141 if (!II_ferror) 142 II_ferror = &Ctx.Idents.get("ferror"); 143 if (!II_fileno) 144 II_fileno = &Ctx.Idents.get("fileno"); 145 146 if (FD->getIdentifier() == II_fopen) { 147 Fopen(C, CE); 148 return true; 149 } 150 if (FD->getIdentifier() == II_tmpfile) { 151 Tmpfile(C, CE); 152 return true; 153 } 154 if (FD->getIdentifier() == II_fclose) { 155 Fclose(C, CE); 156 return true; 157 } 158 if (FD->getIdentifier() == II_fread) { 159 Fread(C, CE); 160 return true; 161 } 162 if (FD->getIdentifier() == II_fwrite) { 163 Fwrite(C, CE); 164 return true; 165 } 166 if (FD->getIdentifier() == II_fseek) { 167 Fseek(C, CE); 168 return true; 169 } 170 if (FD->getIdentifier() == II_ftell) { 171 Ftell(C, CE); 172 return true; 173 } 174 if (FD->getIdentifier() == II_rewind) { 175 Rewind(C, CE); 176 return true; 177 } 178 if (FD->getIdentifier() == II_fgetpos) { 179 Fgetpos(C, CE); 180 return true; 181 } 182 if (FD->getIdentifier() == II_fsetpos) { 183 Fsetpos(C, CE); 184 return true; 185 } 186 if (FD->getIdentifier() == II_clearerr) { 187 Clearerr(C, CE); 188 return true; 189 } 190 if (FD->getIdentifier() == II_feof) { 191 Feof(C, CE); 192 return true; 193 } 194 if (FD->getIdentifier() == II_ferror) { 195 Ferror(C, CE); 196 return true; 197 } 198 if (FD->getIdentifier() == II_fileno) { 199 Fileno(C, CE); 200 return true; 201 } 202 203 return false; 204 } 205 206 void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) const { 207 OpenFileAux(C, CE); 208 } 209 210 void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const { 211 OpenFileAux(C, CE); 212 } 213 214 void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { 215 ProgramStateRef state = C.getState(); 216 SValBuilder &svalBuilder = C.getSValBuilder(); 217 const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); 218 DefinedSVal RetVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, 219 C.blockCount()) 220 .castAs<DefinedSVal>(); 221 state = state->BindExpr(CE, C.getLocationContext(), RetVal); 222 223 ConstraintManager &CM = C.getConstraintManager(); 224 // Bifurcate the state into two: one with a valid FILE* pointer, the other 225 // with a NULL. 226 ProgramStateRef stateNotNull, stateNull; 227 std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); 228 229 if (SymbolRef Sym = RetVal.getAsSymbol()) { 230 // if RetVal is not NULL, set the symbol's state to Opened. 231 stateNotNull = 232 stateNotNull->set<StreamMap>(Sym,StreamState::getOpened(CE)); 233 stateNull = 234 stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed(CE)); 235 236 C.addTransition(stateNotNull); 237 C.addTransition(stateNull); 238 } 239 } 240 241 void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const { 242 ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C); 243 if (state) 244 C.addTransition(state); 245 } 246 247 void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const { 248 ProgramStateRef state = C.getState(); 249 if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C)) 250 return; 251 } 252 253 void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const { 254 ProgramStateRef state = C.getState(); 255 if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C)) 256 return; 257 } 258 259 void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { 260 ProgramStateRef state = C.getState(); 261 if (!(state = CheckNullStream(C.getSVal(CE->getArg(0)), state, C))) 262 return; 263 // Check the legality of the 'whence' argument of 'fseek'. 264 SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext()); 265 Optional<nonloc::ConcreteInt> CI = Whence.getAs<nonloc::ConcreteInt>(); 266 267 if (!CI) 268 return; 269 270 int64_t x = CI->getValue().getSExtValue(); 271 if (x >= 0 && x <= 2) 272 return; 273 274 if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { 275 if (!BT_illegalwhence) 276 BT_illegalwhence.reset( 277 new BuiltinBug(this, "Illegal whence argument", 278 "The whence argument to fseek() should be " 279 "SEEK_SET, SEEK_END, or SEEK_CUR.")); 280 C.emitReport(llvm::make_unique<BugReport>( 281 *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); 282 } 283 } 284 285 void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const { 286 ProgramStateRef state = C.getState(); 287 if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) 288 return; 289 } 290 291 void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const { 292 ProgramStateRef state = C.getState(); 293 if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) 294 return; 295 } 296 297 void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const { 298 ProgramStateRef state = C.getState(); 299 if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) 300 return; 301 } 302 303 void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const { 304 ProgramStateRef state = C.getState(); 305 if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) 306 return; 307 } 308 309 void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const { 310 ProgramStateRef state = C.getState(); 311 if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) 312 return; 313 } 314 315 void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const { 316 ProgramStateRef state = C.getState(); 317 if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) 318 return; 319 } 320 321 void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const { 322 ProgramStateRef state = C.getState(); 323 if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) 324 return; 325 } 326 327 void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const { 328 ProgramStateRef state = C.getState(); 329 if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) 330 return; 331 } 332 333 ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, 334 CheckerContext &C) const { 335 Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>(); 336 if (!DV) 337 return nullptr; 338 339 ConstraintManager &CM = C.getConstraintManager(); 340 ProgramStateRef stateNotNull, stateNull; 341 std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); 342 343 if (!stateNotNull && stateNull) { 344 if (ExplodedNode *N = C.generateErrorNode(stateNull)) { 345 if (!BT_nullfp) 346 BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", 347 "Stream pointer might be NULL.")); 348 C.emitReport(llvm::make_unique<BugReport>( 349 *BT_nullfp, BT_nullfp->getDescription(), N)); 350 } 351 return nullptr; 352 } 353 return stateNotNull; 354 } 355 356 ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, 357 ProgramStateRef state, 358 CheckerContext &C) const { 359 SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol(); 360 if (!Sym) 361 return state; 362 363 const StreamState *SS = state->get<StreamMap>(Sym); 364 365 // If the file stream is not tracked, return. 366 if (!SS) 367 return state; 368 369 // Check: Double close a File Descriptor could cause undefined behaviour. 370 // Conforming to man-pages 371 if (SS->isClosed()) { 372 ExplodedNode *N = C.generateErrorNode(); 373 if (N) { 374 if (!BT_doubleclose) 375 BT_doubleclose.reset(new BuiltinBug( 376 this, "Double fclose", "Try to close a file Descriptor already" 377 " closed. Cause undefined behaviour.")); 378 C.emitReport(llvm::make_unique<BugReport>( 379 *BT_doubleclose, BT_doubleclose->getDescription(), N)); 380 } 381 return nullptr; 382 } 383 384 // Close the File Descriptor. 385 return state->set<StreamMap>(Sym, StreamState::getClosed(CE)); 386 } 387 388 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 389 CheckerContext &C) const { 390 ProgramStateRef state = C.getState(); 391 392 // TODO: Clean up the state. 393 const StreamMapTy &Map = state->get<StreamMap>(); 394 for (const auto &I: Map) { 395 SymbolRef Sym = I.first; 396 const StreamState &SS = I.second; 397 if (!SymReaper.isDead(Sym) || !SS.isOpened()) 398 continue; 399 400 ExplodedNode *N = C.generateErrorNode(); 401 if (!N) 402 return; 403 404 if (!BT_ResourceLeak) 405 BT_ResourceLeak.reset( 406 new BuiltinBug(this, "Resource Leak", 407 "Opened File never closed. Potential Resource leak.")); 408 C.emitReport(llvm::make_unique<BugReport>( 409 *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); 410 } 411 } 412 413 void ento::registerStreamChecker(CheckerManager &mgr) { 414 mgr.registerChecker<StreamChecker>(); 415 } 416 417 bool ento::shouldRegisterStreamChecker(const LangOptions &LO) { 418 return true; 419 } 420