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