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