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 FnDescription; 31 32 /// State of the stream error flags. 33 /// Sometimes it is not known to the checker what error flags are set. 34 /// This is indicated by setting more than one flag to true. 35 /// This is an optimization to avoid state splits. 36 /// A stream can either be in FEOF or FERROR but not both at the same time. 37 /// Multiple flags are set to handle the corresponding states together. 38 struct StreamErrorState { 39 /// The stream can be in state where none of the error flags set. 40 bool NoError = true; 41 /// The stream can be in state where the EOF indicator is set. 42 bool FEof = false; 43 /// The stream can be in state where the error indicator is set. 44 bool FError = false; 45 46 bool isNoError() const { return NoError && !FEof && !FError; } 47 bool isFEof() const { return !NoError && FEof && !FError; } 48 bool isFError() const { return !NoError && !FEof && FError; } 49 50 bool operator==(const StreamErrorState &ES) const { 51 return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError; 52 } 53 54 bool operator!=(const StreamErrorState &ES) const { return !(*this == ES); } 55 56 StreamErrorState operator|(const StreamErrorState &E) const { 57 return {NoError || E.NoError, FEof || E.FEof, FError || E.FError}; 58 } 59 60 StreamErrorState operator&(const StreamErrorState &E) const { 61 return {NoError && E.NoError, FEof && E.FEof, FError && E.FError}; 62 } 63 64 StreamErrorState operator~() const { return {!NoError, !FEof, !FError}; } 65 66 /// Returns if the StreamErrorState is a valid object. 67 operator bool() const { return NoError || FEof || FError; } 68 69 void Profile(llvm::FoldingSetNodeID &ID) const { 70 ID.AddBoolean(NoError); 71 ID.AddBoolean(FEof); 72 ID.AddBoolean(FError); 73 } 74 }; 75 76 const StreamErrorState ErrorNone{true, false, false}; 77 const StreamErrorState ErrorFEof{false, true, false}; 78 const StreamErrorState ErrorFError{false, false, true}; 79 80 /// Full state information about a stream pointer. 81 struct StreamState { 82 /// The last file operation called in the stream. 83 const FnDescription *LastOperation; 84 85 /// State of a stream symbol. 86 /// FIXME: We need maybe an "escaped" state later. 87 enum KindTy { 88 Opened, /// Stream is opened. 89 Closed, /// Closed stream (an invalid stream pointer after it was closed). 90 OpenFailed /// The last open operation has failed. 91 } State; 92 93 /// State of the error flags. 94 /// Ignored in non-opened stream state but must be NoError. 95 StreamErrorState const ErrorState; 96 97 /// Indicate if the file has an "indeterminate file position indicator". 98 /// This can be set at a failing read or write or seek operation. 99 /// If it is set no more read or write is allowed. 100 /// This value is not dependent on the stream error flags: 101 /// The error flag may be cleared with `clearerr` but the file position 102 /// remains still indeterminate. 103 /// This value applies to all error states in ErrorState except FEOF. 104 /// An EOF+indeterminate state is the same as EOF state. 105 bool const FilePositionIndeterminate = false; 106 107 StreamState(const FnDescription *L, KindTy S, const StreamErrorState &ES, 108 bool IsFilePositionIndeterminate) 109 : LastOperation(L), State(S), ErrorState(ES), 110 FilePositionIndeterminate(IsFilePositionIndeterminate) { 111 assert((!ES.isFEof() || !IsFilePositionIndeterminate) && 112 "FilePositionIndeterminate should be false in FEof case."); 113 assert((State == Opened || ErrorState.isNoError()) && 114 "ErrorState should be None in non-opened stream state."); 115 } 116 117 bool isOpened() const { return State == Opened; } 118 bool isClosed() const { return State == Closed; } 119 bool isOpenFailed() const { return State == OpenFailed; } 120 121 bool operator==(const StreamState &X) const { 122 // In not opened state error state should always NoError, so comparison 123 // here is no problem. 124 return LastOperation == X.LastOperation && State == X.State && 125 ErrorState == X.ErrorState && 126 FilePositionIndeterminate == X.FilePositionIndeterminate; 127 } 128 129 static StreamState getOpened(const FnDescription *L, 130 const StreamErrorState &ES = ErrorNone, 131 bool IsFilePositionIndeterminate = false) { 132 return StreamState{L, Opened, ES, IsFilePositionIndeterminate}; 133 } 134 static StreamState getClosed(const FnDescription *L) { 135 return StreamState{L, Closed, {}, false}; 136 } 137 static StreamState getOpenFailed(const FnDescription *L) { 138 return StreamState{L, OpenFailed, {}, false}; 139 } 140 141 void Profile(llvm::FoldingSetNodeID &ID) const { 142 ID.AddPointer(LastOperation); 143 ID.AddInteger(State); 144 ID.AddInteger(ErrorState); 145 ID.AddBoolean(FilePositionIndeterminate); 146 } 147 }; 148 149 class StreamChecker; 150 using FnCheck = std::function<void(const StreamChecker *, const FnDescription *, 151 const CallEvent &, CheckerContext &)>; 152 153 using ArgNoTy = unsigned int; 154 static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max(); 155 156 struct FnDescription { 157 FnCheck PreFn; 158 FnCheck EvalFn; 159 ArgNoTy StreamArgNo; 160 }; 161 162 /// Get the value of the stream argument out of the passed call event. 163 /// The call should contain a function that is described by Desc. 164 SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) { 165 assert(Desc && Desc->StreamArgNo != ArgNone && 166 "Try to get a non-existing stream argument."); 167 return Call.getArgSVal(Desc->StreamArgNo); 168 } 169 170 /// Create a conjured symbol return value for a call expression. 171 DefinedSVal makeRetVal(CheckerContext &C, const CallExpr *CE) { 172 assert(CE && "Expecting a call expression."); 173 174 const LocationContext *LCtx = C.getLocationContext(); 175 return C.getSValBuilder() 176 .conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) 177 .castAs<DefinedSVal>(); 178 } 179 180 ProgramStateRef bindAndAssumeTrue(ProgramStateRef State, CheckerContext &C, 181 const CallExpr *CE) { 182 DefinedSVal RetVal = makeRetVal(C, CE); 183 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 184 State = State->assume(RetVal, true); 185 assert(State && "Assumption on new value should not fail."); 186 return State; 187 } 188 189 ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State, 190 CheckerContext &C, const CallExpr *CE) { 191 State = State->BindExpr(CE, C.getLocationContext(), 192 C.getSValBuilder().makeIntVal(Value, false)); 193 return State; 194 } 195 196 class StreamChecker : public Checker<check::PreCall, eval::Call, 197 check::DeadSymbols, check::PointerEscape> { 198 BugType BT_FileNull{this, "NULL stream pointer", "Stream handling error"}; 199 BugType BT_UseAfterClose{this, "Closed stream", "Stream handling error"}; 200 BugType BT_UseAfterOpenFailed{this, "Invalid stream", 201 "Stream handling error"}; 202 BugType BT_IndeterminatePosition{this, "Invalid stream state", 203 "Stream handling error"}; 204 BugType BT_IllegalWhence{this, "Illegal whence argument", 205 "Stream handling error"}; 206 BugType BT_StreamEof{this, "Stream already in EOF", "Stream handling error"}; 207 BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error"}; 208 209 public: 210 void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 211 bool evalCall(const CallEvent &Call, CheckerContext &C) const; 212 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 213 ProgramStateRef checkPointerEscape(ProgramStateRef State, 214 const InvalidatedSymbols &Escaped, 215 const CallEvent *Call, 216 PointerEscapeKind Kind) const; 217 218 /// If true, evaluate special testing stream functions. 219 bool TestMode = false; 220 221 private: 222 CallDescriptionMap<FnDescription> FnDescriptions = { 223 {{"fopen"}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, 224 {{"freopen", 3}, 225 {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}}, 226 {{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, 227 {{"fclose", 1}, 228 {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}}, 229 {{"fread", 4}, 230 {&StreamChecker::preFread, 231 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}}, 232 {{"fwrite", 4}, 233 {&StreamChecker::preFwrite, 234 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}}, 235 {{"fseek", 3}, {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}}, 236 {{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0}}, 237 {{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0}}, 238 {{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}}, 239 {{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}}, 240 {{"clearerr", 1}, 241 {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}}, 242 {{"feof", 1}, 243 {&StreamChecker::preDefault, 244 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof), 245 0}}, 246 {{"ferror", 1}, 247 {&StreamChecker::preDefault, 248 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError), 249 0}}, 250 {{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0}}, 251 }; 252 253 CallDescriptionMap<FnDescription> FnTestDescriptions = { 254 {{"StreamTesterChecker_make_feof_stream", 1}, 255 {nullptr, 256 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof), 257 0}}, 258 {{"StreamTesterChecker_make_ferror_stream", 1}, 259 {nullptr, 260 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, 261 ErrorFError), 262 0}}, 263 }; 264 265 void evalFopen(const FnDescription *Desc, const CallEvent &Call, 266 CheckerContext &C) const; 267 268 void preFreopen(const FnDescription *Desc, const CallEvent &Call, 269 CheckerContext &C) const; 270 void evalFreopen(const FnDescription *Desc, const CallEvent &Call, 271 CheckerContext &C) const; 272 273 void evalFclose(const FnDescription *Desc, const CallEvent &Call, 274 CheckerContext &C) const; 275 276 void preFread(const FnDescription *Desc, const CallEvent &Call, 277 CheckerContext &C) const; 278 279 void preFwrite(const FnDescription *Desc, const CallEvent &Call, 280 CheckerContext &C) const; 281 282 void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call, 283 CheckerContext &C, bool IsFread) const; 284 285 void preFseek(const FnDescription *Desc, const CallEvent &Call, 286 CheckerContext &C) const; 287 void evalFseek(const FnDescription *Desc, const CallEvent &Call, 288 CheckerContext &C) const; 289 290 void preDefault(const FnDescription *Desc, const CallEvent &Call, 291 CheckerContext &C) const; 292 293 void evalClearerr(const FnDescription *Desc, const CallEvent &Call, 294 CheckerContext &C) const; 295 296 void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call, 297 CheckerContext &C, 298 const StreamErrorState &ErrorKind) const; 299 300 void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call, 301 CheckerContext &C, 302 const StreamErrorState &ErrorKind) const; 303 304 /// Check that the stream (in StreamVal) is not NULL. 305 /// If it can only be NULL a fatal error is emitted and nullptr returned. 306 /// Otherwise the return value is a new state where the stream is constrained 307 /// to be non-null. 308 ProgramStateRef ensureStreamNonNull(SVal StreamVal, CheckerContext &C, 309 ProgramStateRef State) const; 310 311 /// Check that the stream is the opened state. 312 /// If the stream is known to be not opened an error is generated 313 /// and nullptr returned, otherwise the original state is returned. 314 ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C, 315 ProgramStateRef State) const; 316 317 /// Check that the stream has not an invalid ("indeterminate") file position, 318 /// generate warning for it. 319 /// (EOF is not an invalid position.) 320 /// The returned state can be nullptr if a fatal error was generated. 321 /// It can return non-null state if the stream has not an invalid position or 322 /// there is execution path with non-invalid position. 323 ProgramStateRef 324 ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C, 325 ProgramStateRef State) const; 326 327 /// Check the legality of the 'whence' argument of 'fseek'. 328 /// Generate error and return nullptr if it is found to be illegal. 329 /// Otherwise returns the state. 330 /// (State is not changed here because the "whence" value is already known.) 331 ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 332 ProgramStateRef State) const; 333 334 /// Generate warning about stream in EOF state. 335 /// There will be always a state transition into the passed State, 336 /// by the new non-fatal error node or (if failed) a normal transition, 337 /// to ensure uniform handling. 338 void reportFEofWarning(CheckerContext &C, ProgramStateRef State) const; 339 340 /// Emit resource leak warnings for the given symbols. 341 /// Createn a non-fatal error node for these, and returns it (if any warnings 342 /// were generated). Return value is non-null. 343 ExplodedNode *reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms, 344 CheckerContext &C, ExplodedNode *Pred) const; 345 346 /// Find the description data of the function called by a call event. 347 /// Returns nullptr if no function is recognized. 348 const FnDescription *lookupFn(const CallEvent &Call) const { 349 // Recognize "global C functions" with only integral or pointer arguments 350 // (and matching name) as stream functions. 351 if (!Call.isGlobalCFunction()) 352 return nullptr; 353 for (auto P : Call.parameters()) { 354 QualType T = P->getType(); 355 if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) 356 return nullptr; 357 } 358 359 return FnDescriptions.lookup(Call); 360 } 361 362 /// Generate a message for BugReporterVisitor if the stored symbol is 363 /// marked as interesting by the actual bug report. 364 struct NoteFn { 365 const CheckerNameRef CheckerName; 366 SymbolRef StreamSym; 367 std::string Message; 368 369 std::string operator()(PathSensitiveBugReport &BR) const { 370 if (BR.isInteresting(StreamSym) && 371 CheckerName == BR.getBugType().getCheckerName()) 372 return Message; 373 374 return ""; 375 } 376 }; 377 378 const NoteTag *constructNoteTag(CheckerContext &C, SymbolRef StreamSym, 379 const std::string &Message) const { 380 return C.getNoteTag(NoteFn{getCheckerName(), StreamSym, Message}); 381 } 382 383 /// Searches for the ExplodedNode where the file descriptor was acquired for 384 /// StreamSym. 385 static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N, 386 SymbolRef StreamSym, 387 CheckerContext &C); 388 }; 389 390 } // end anonymous namespace 391 392 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 393 394 inline void assertStreamStateOpened(const StreamState *SS) { 395 assert(SS->isOpened() && 396 "Previous create of error node for non-opened stream failed?"); 397 } 398 399 const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, 400 SymbolRef StreamSym, 401 CheckerContext &C) { 402 ProgramStateRef State = N->getState(); 403 // When bug type is resource leak, exploded node N may not have state info 404 // for leaked file descriptor, but predecessor should have it. 405 if (!State->get<StreamMap>(StreamSym)) 406 N = N->getFirstPred(); 407 408 const ExplodedNode *Pred = N; 409 while (N) { 410 State = N->getState(); 411 if (!State->get<StreamMap>(StreamSym)) 412 return Pred; 413 Pred = N; 414 N = N->getFirstPred(); 415 } 416 417 return nullptr; 418 } 419 420 void StreamChecker::checkPreCall(const CallEvent &Call, 421 CheckerContext &C) const { 422 const FnDescription *Desc = lookupFn(Call); 423 if (!Desc || !Desc->PreFn) 424 return; 425 426 Desc->PreFn(this, Desc, Call, C); 427 } 428 429 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { 430 const FnDescription *Desc = lookupFn(Call); 431 if (!Desc && TestMode) 432 Desc = FnTestDescriptions.lookup(Call); 433 if (!Desc || !Desc->EvalFn) 434 return false; 435 436 Desc->EvalFn(this, Desc, Call, C); 437 438 return C.isDifferent(); 439 } 440 441 void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, 442 CheckerContext &C) const { 443 ProgramStateRef State = C.getState(); 444 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 445 if (!CE) 446 return; 447 448 DefinedSVal RetVal = makeRetVal(C, CE); 449 SymbolRef RetSym = RetVal.getAsSymbol(); 450 assert(RetSym && "RetVal must be a symbol here."); 451 452 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 453 454 // Bifurcate the state into two: one with a valid FILE* pointer, the other 455 // with a NULL. 456 ProgramStateRef StateNotNull, StateNull; 457 std::tie(StateNotNull, StateNull) = 458 C.getConstraintManager().assumeDual(State, RetVal); 459 460 StateNotNull = 461 StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc)); 462 StateNull = 463 StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc)); 464 465 C.addTransition(StateNotNull, 466 constructNoteTag(C, RetSym, "Stream opened here")); 467 C.addTransition(StateNull); 468 } 469 470 void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, 471 CheckerContext &C) const { 472 // Do not allow NULL as passed stream pointer but allow a closed stream. 473 ProgramStateRef State = C.getState(); 474 State = ensureStreamNonNull(getStreamArg(Desc, Call), C, State); 475 if (!State) 476 return; 477 478 C.addTransition(State); 479 } 480 481 void StreamChecker::evalFreopen(const FnDescription *Desc, 482 const CallEvent &Call, 483 CheckerContext &C) const { 484 ProgramStateRef State = C.getState(); 485 486 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 487 if (!CE) 488 return; 489 490 Optional<DefinedSVal> StreamVal = 491 getStreamArg(Desc, Call).getAs<DefinedSVal>(); 492 if (!StreamVal) 493 return; 494 495 SymbolRef StreamSym = StreamVal->getAsSymbol(); 496 // Do not care about concrete values for stream ("(FILE *)0x12345"?). 497 // FIXME: Can be stdin, stdout, stderr such values? 498 if (!StreamSym) 499 return; 500 501 // Do not handle untracked stream. It is probably escaped. 502 if (!State->get<StreamMap>(StreamSym)) 503 return; 504 505 // Generate state for non-failed case. 506 // Return value is the passed stream pointer. 507 // According to the documentations, the stream is closed first 508 // but any close error is ignored. The state changes to (or remains) opened. 509 ProgramStateRef StateRetNotNull = 510 State->BindExpr(CE, C.getLocationContext(), *StreamVal); 511 // Generate state for NULL return value. 512 // Stream switches to OpenFailed state. 513 ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(), 514 C.getSValBuilder().makeNull()); 515 516 StateRetNotNull = 517 StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); 518 StateRetNull = 519 StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc)); 520 521 C.addTransition(StateRetNotNull, 522 constructNoteTag(C, StreamSym, "Stream reopened here")); 523 C.addTransition(StateRetNull); 524 } 525 526 void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, 527 CheckerContext &C) const { 528 ProgramStateRef State = C.getState(); 529 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); 530 if (!Sym) 531 return; 532 533 const StreamState *SS = State->get<StreamMap>(Sym); 534 if (!SS) 535 return; 536 537 assertStreamStateOpened(SS); 538 539 // Close the File Descriptor. 540 // Regardless if the close fails or not, stream becomes "closed" 541 // and can not be used any more. 542 State = State->set<StreamMap>(Sym, StreamState::getClosed(Desc)); 543 544 C.addTransition(State); 545 } 546 547 void StreamChecker::preFread(const FnDescription *Desc, const CallEvent &Call, 548 CheckerContext &C) const { 549 ProgramStateRef State = C.getState(); 550 SVal StreamVal = getStreamArg(Desc, Call); 551 State = ensureStreamNonNull(StreamVal, C, State); 552 if (!State) 553 return; 554 State = ensureStreamOpened(StreamVal, C, State); 555 if (!State) 556 return; 557 State = ensureNoFilePositionIndeterminate(StreamVal, C, State); 558 if (!State) 559 return; 560 561 SymbolRef Sym = StreamVal.getAsSymbol(); 562 if (Sym && State->get<StreamMap>(Sym)) { 563 const StreamState *SS = State->get<StreamMap>(Sym); 564 if (SS->ErrorState & ErrorFEof) 565 reportFEofWarning(C, State); 566 } else { 567 C.addTransition(State); 568 } 569 } 570 571 void StreamChecker::preFwrite(const FnDescription *Desc, const CallEvent &Call, 572 CheckerContext &C) const { 573 ProgramStateRef State = C.getState(); 574 SVal StreamVal = getStreamArg(Desc, Call); 575 State = ensureStreamNonNull(StreamVal, C, State); 576 if (!State) 577 return; 578 State = ensureStreamOpened(StreamVal, C, State); 579 if (!State) 580 return; 581 State = ensureNoFilePositionIndeterminate(StreamVal, C, State); 582 if (!State) 583 return; 584 585 C.addTransition(State); 586 } 587 588 void StreamChecker::evalFreadFwrite(const FnDescription *Desc, 589 const CallEvent &Call, CheckerContext &C, 590 bool IsFread) const { 591 ProgramStateRef State = C.getState(); 592 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 593 if (!StreamSym) 594 return; 595 596 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 597 if (!CE) 598 return; 599 600 Optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>(); 601 if (!SizeVal) 602 return; 603 Optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>(); 604 if (!NMembVal) 605 return; 606 607 const StreamState *SS = State->get<StreamMap>(StreamSym); 608 if (!SS) 609 return; 610 611 assertStreamStateOpened(SS); 612 613 // C'99 standard, §7.19.8.1.3, the return value of fread: 614 // The fread function returns the number of elements successfully read, which 615 // may be less than nmemb if a read error or end-of-file is encountered. If 616 // size or nmemb is zero, fread returns zero and the contents of the array and 617 // the state of the stream remain unchanged. 618 619 if (State->isNull(*SizeVal).isConstrainedTrue() || 620 State->isNull(*NMembVal).isConstrainedTrue()) { 621 // This is the "size or nmemb is zero" case. 622 // Just return 0, do nothing more (not clear the error flags). 623 State = bindInt(0, State, C, CE); 624 C.addTransition(State); 625 return; 626 } 627 628 // Generate a transition for the success state. 629 // If we know the state to be FEOF at fread, do not add a success state. 630 if (!IsFread || (SS->ErrorState != ErrorFEof)) { 631 ProgramStateRef StateNotFailed = 632 State->BindExpr(CE, C.getLocationContext(), *NMembVal); 633 if (StateNotFailed) { 634 StateNotFailed = StateNotFailed->set<StreamMap>( 635 StreamSym, StreamState::getOpened(Desc)); 636 C.addTransition(StateNotFailed); 637 } 638 } 639 640 // Add transition for the failed state. 641 Optional<NonLoc> RetVal = makeRetVal(C, CE).castAs<NonLoc>(); 642 assert(RetVal && "Value should be NonLoc."); 643 ProgramStateRef StateFailed = 644 State->BindExpr(CE, C.getLocationContext(), *RetVal); 645 if (!StateFailed) 646 return; 647 auto Cond = C.getSValBuilder() 648 .evalBinOpNN(State, BO_LT, *RetVal, *NMembVal, 649 C.getASTContext().IntTy) 650 .getAs<DefinedOrUnknownSVal>(); 651 if (!Cond) 652 return; 653 StateFailed = StateFailed->assume(*Cond, true); 654 if (!StateFailed) 655 return; 656 657 StreamErrorState NewES; 658 if (IsFread) 659 NewES = (SS->ErrorState == ErrorFEof) ? ErrorFEof : ErrorFEof | ErrorFError; 660 else 661 NewES = ErrorFError; 662 // If a (non-EOF) error occurs, the resulting value of the file position 663 // indicator for the stream is indeterminate. 664 StreamState NewState = StreamState::getOpened(Desc, NewES, !NewES.isFEof()); 665 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewState); 666 C.addTransition(StateFailed); 667 } 668 669 void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, 670 CheckerContext &C) const { 671 ProgramStateRef State = C.getState(); 672 SVal StreamVal = getStreamArg(Desc, Call); 673 State = ensureStreamNonNull(StreamVal, C, State); 674 if (!State) 675 return; 676 State = ensureStreamOpened(StreamVal, C, State); 677 if (!State) 678 return; 679 State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State); 680 if (!State) 681 return; 682 683 C.addTransition(State); 684 } 685 686 void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call, 687 CheckerContext &C) const { 688 ProgramStateRef State = C.getState(); 689 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 690 if (!StreamSym) 691 return; 692 693 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 694 if (!CE) 695 return; 696 697 // Ignore the call if the stream is not tracked. 698 if (!State->get<StreamMap>(StreamSym)) 699 return; 700 701 DefinedSVal RetVal = makeRetVal(C, CE); 702 703 // Make expression result. 704 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 705 706 // Bifurcate the state into failed and non-failed. 707 // Return zero on success, nonzero on error. 708 ProgramStateRef StateNotFailed, StateFailed; 709 std::tie(StateFailed, StateNotFailed) = 710 C.getConstraintManager().assumeDual(State, RetVal); 711 712 // Reset the state to opened with no error. 713 StateNotFailed = 714 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); 715 // We get error. 716 // It is possible that fseek fails but sets none of the error flags. 717 // If fseek failed, assume that the file position becomes indeterminate in any 718 // case. 719 StateFailed = StateFailed->set<StreamMap>( 720 StreamSym, 721 StreamState::getOpened(Desc, ErrorNone | ErrorFEof | ErrorFError, true)); 722 723 C.addTransition(StateNotFailed); 724 C.addTransition(StateFailed); 725 } 726 727 void StreamChecker::evalClearerr(const FnDescription *Desc, 728 const CallEvent &Call, 729 CheckerContext &C) const { 730 ProgramStateRef State = C.getState(); 731 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 732 if (!StreamSym) 733 return; 734 735 const StreamState *SS = State->get<StreamMap>(StreamSym); 736 if (!SS) 737 return; 738 739 assertStreamStateOpened(SS); 740 741 // FilePositionIndeterminate is not cleared. 742 State = State->set<StreamMap>( 743 StreamSym, 744 StreamState::getOpened(Desc, ErrorNone, SS->FilePositionIndeterminate)); 745 C.addTransition(State); 746 } 747 748 void StreamChecker::evalFeofFerror(const FnDescription *Desc, 749 const CallEvent &Call, CheckerContext &C, 750 const StreamErrorState &ErrorKind) const { 751 ProgramStateRef State = C.getState(); 752 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 753 if (!StreamSym) 754 return; 755 756 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 757 if (!CE) 758 return; 759 760 const StreamState *SS = State->get<StreamMap>(StreamSym); 761 if (!SS) 762 return; 763 764 assertStreamStateOpened(SS); 765 766 if (SS->ErrorState & ErrorKind) { 767 // Execution path with error of ErrorKind. 768 // Function returns true. 769 // From now on it is the only one error state. 770 ProgramStateRef TrueState = bindAndAssumeTrue(State, C, CE); 771 C.addTransition(TrueState->set<StreamMap>( 772 StreamSym, StreamState::getOpened(Desc, ErrorKind, 773 SS->FilePositionIndeterminate && 774 !ErrorKind.isFEof()))); 775 } 776 if (StreamErrorState NewES = SS->ErrorState & (~ErrorKind)) { 777 // Execution path(s) with ErrorKind not set. 778 // Function returns false. 779 // New error state is everything before minus ErrorKind. 780 ProgramStateRef FalseState = bindInt(0, State, C, CE); 781 C.addTransition(FalseState->set<StreamMap>( 782 StreamSym, 783 StreamState::getOpened( 784 Desc, NewES, SS->FilePositionIndeterminate && !NewES.isFEof()))); 785 } 786 } 787 788 void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call, 789 CheckerContext &C) const { 790 ProgramStateRef State = C.getState(); 791 SVal StreamVal = getStreamArg(Desc, Call); 792 State = ensureStreamNonNull(StreamVal, C, State); 793 if (!State) 794 return; 795 State = ensureStreamOpened(StreamVal, C, State); 796 if (!State) 797 return; 798 799 C.addTransition(State); 800 } 801 802 void StreamChecker::evalSetFeofFerror(const FnDescription *Desc, 803 const CallEvent &Call, CheckerContext &C, 804 const StreamErrorState &ErrorKind) const { 805 ProgramStateRef State = C.getState(); 806 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 807 assert(StreamSym && "Operation not permitted on non-symbolic stream value."); 808 const StreamState *SS = State->get<StreamMap>(StreamSym); 809 assert(SS && "Stream should be tracked by the checker."); 810 State = State->set<StreamMap>( 811 StreamSym, StreamState::getOpened(SS->LastOperation, ErrorKind)); 812 C.addTransition(State); 813 } 814 815 ProgramStateRef 816 StreamChecker::ensureStreamNonNull(SVal StreamVal, CheckerContext &C, 817 ProgramStateRef State) const { 818 auto Stream = StreamVal.getAs<DefinedSVal>(); 819 if (!Stream) 820 return State; 821 822 ConstraintManager &CM = C.getConstraintManager(); 823 824 ProgramStateRef StateNotNull, StateNull; 825 std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *Stream); 826 827 if (!StateNotNull && StateNull) { 828 if (ExplodedNode *N = C.generateErrorNode(StateNull)) { 829 C.emitReport(std::make_unique<PathSensitiveBugReport>( 830 BT_FileNull, "Stream pointer might be NULL.", N)); 831 } 832 return nullptr; 833 } 834 835 return StateNotNull; 836 } 837 838 ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal, 839 CheckerContext &C, 840 ProgramStateRef State) const { 841 SymbolRef Sym = StreamVal.getAsSymbol(); 842 if (!Sym) 843 return State; 844 845 const StreamState *SS = State->get<StreamMap>(Sym); 846 if (!SS) 847 return State; 848 849 if (SS->isClosed()) { 850 // Using a stream pointer after 'fclose' causes undefined behavior 851 // according to cppreference.com . 852 ExplodedNode *N = C.generateErrorNode(); 853 if (N) { 854 C.emitReport(std::make_unique<PathSensitiveBugReport>( 855 BT_UseAfterClose, 856 "Stream might be already closed. Causes undefined behaviour.", N)); 857 return nullptr; 858 } 859 860 return State; 861 } 862 863 if (SS->isOpenFailed()) { 864 // Using a stream that has failed to open is likely to cause problems. 865 // This should usually not occur because stream pointer is NULL. 866 // But freopen can cause a state when stream pointer remains non-null but 867 // failed to open. 868 ExplodedNode *N = C.generateErrorNode(); 869 if (N) { 870 C.emitReport(std::make_unique<PathSensitiveBugReport>( 871 BT_UseAfterOpenFailed, 872 "Stream might be invalid after " 873 "(re-)opening it has failed. " 874 "Can cause undefined behaviour.", 875 N)); 876 return nullptr; 877 } 878 return State; 879 } 880 881 return State; 882 } 883 884 ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate( 885 SVal StreamVal, CheckerContext &C, ProgramStateRef State) const { 886 static const char *BugMessage = 887 "File position of the stream might be 'indeterminate' " 888 "after a failed operation. " 889 "Can cause undefined behavior."; 890 891 SymbolRef Sym = StreamVal.getAsSymbol(); 892 if (!Sym) 893 return State; 894 895 const StreamState *SS = State->get<StreamMap>(Sym); 896 if (!SS) 897 return State; 898 899 assert(SS->isOpened() && "First ensure that stream is opened."); 900 901 if (SS->FilePositionIndeterminate) { 902 if (SS->ErrorState & ErrorFEof) { 903 // The error is unknown but may be FEOF. 904 // Continue analysis with the FEOF error state. 905 // Report warning because the other possible error states. 906 ExplodedNode *N = C.generateNonFatalErrorNode(State); 907 if (!N) 908 return nullptr; 909 910 C.emitReport(std::make_unique<PathSensitiveBugReport>( 911 BT_IndeterminatePosition, BugMessage, N)); 912 return State->set<StreamMap>( 913 Sym, StreamState::getOpened(SS->LastOperation, ErrorFEof, false)); 914 } 915 916 // Known or unknown error state without FEOF possible. 917 // Stop analysis, report error. 918 ExplodedNode *N = C.generateErrorNode(State); 919 if (N) 920 C.emitReport(std::make_unique<PathSensitiveBugReport>( 921 BT_IndeterminatePosition, BugMessage, N)); 922 923 return nullptr; 924 } 925 926 return State; 927 } 928 929 ProgramStateRef 930 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 931 ProgramStateRef State) const { 932 Optional<nonloc::ConcreteInt> CI = WhenceVal.getAs<nonloc::ConcreteInt>(); 933 if (!CI) 934 return State; 935 936 int64_t X = CI->getValue().getSExtValue(); 937 if (X >= 0 && X <= 2) 938 return State; 939 940 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 941 C.emitReport(std::make_unique<PathSensitiveBugReport>( 942 BT_IllegalWhence, 943 "The whence argument to fseek() should be " 944 "SEEK_SET, SEEK_END, or SEEK_CUR.", 945 N)); 946 return nullptr; 947 } 948 949 return State; 950 } 951 952 void StreamChecker::reportFEofWarning(CheckerContext &C, 953 ProgramStateRef State) const { 954 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 955 C.emitReport(std::make_unique<PathSensitiveBugReport>( 956 BT_StreamEof, 957 "Read function called when stream is in EOF state. " 958 "Function has no effect.", 959 N)); 960 return; 961 } 962 C.addTransition(State); 963 } 964 965 ExplodedNode * 966 StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms, 967 CheckerContext &C, ExplodedNode *Pred) const { 968 // Do not warn for non-closed stream at program exit. 969 // FIXME: Use BugType::SuppressOnSink instead. 970 if (Pred && Pred->getCFGBlock() && Pred->getCFGBlock()->hasNoReturnElement()) 971 return Pred; 972 973 ExplodedNode *Err = C.generateNonFatalErrorNode(C.getState(), Pred); 974 if (!Err) 975 return Pred; 976 977 for (SymbolRef LeakSym : LeakedSyms) { 978 // Resource leaks can result in multiple warning that describe the same kind 979 // of programming error: 980 // void f() { 981 // FILE *F = fopen("a.txt"); 982 // if (rand()) // state split 983 // return; // warning 984 // } // warning 985 // While this isn't necessarily true (leaking the same stream could result 986 // from a different kinds of errors), the reduction in redundant reports 987 // makes this a worthwhile heuristic. 988 // FIXME: Add a checker option to turn this uniqueing feature off. 989 const ExplodedNode *StreamOpenNode = getAcquisitionSite(Err, LeakSym, C); 990 assert(StreamOpenNode && "Could not find place of stream opening."); 991 PathDiagnosticLocation LocUsedForUniqueing = 992 PathDiagnosticLocation::createBegin( 993 StreamOpenNode->getStmtForDiagnostics(), C.getSourceManager(), 994 StreamOpenNode->getLocationContext()); 995 996 std::unique_ptr<PathSensitiveBugReport> R = 997 std::make_unique<PathSensitiveBugReport>( 998 BT_ResourceLeak, 999 "Opened stream never closed. Potential resource leak.", Err, 1000 LocUsedForUniqueing, 1001 StreamOpenNode->getLocationContext()->getDecl()); 1002 R->markInteresting(LeakSym); 1003 C.emitReport(std::move(R)); 1004 } 1005 1006 return Err; 1007 } 1008 1009 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 1010 CheckerContext &C) const { 1011 ProgramStateRef State = C.getState(); 1012 1013 llvm::SmallVector<SymbolRef, 2> LeakedSyms; 1014 1015 const StreamMapTy &Map = State->get<StreamMap>(); 1016 for (const auto &I : Map) { 1017 SymbolRef Sym = I.first; 1018 const StreamState &SS = I.second; 1019 if (!SymReaper.isDead(Sym)) 1020 continue; 1021 if (SS.isOpened()) 1022 LeakedSyms.push_back(Sym); 1023 State = State->remove<StreamMap>(Sym); 1024 } 1025 1026 ExplodedNode *N = C.getPredecessor(); 1027 if (!LeakedSyms.empty()) 1028 N = reportLeaks(LeakedSyms, C, N); 1029 1030 C.addTransition(State, N); 1031 } 1032 1033 ProgramStateRef StreamChecker::checkPointerEscape( 1034 ProgramStateRef State, const InvalidatedSymbols &Escaped, 1035 const CallEvent *Call, PointerEscapeKind Kind) const { 1036 // Check for file-handling system call that is not handled by the checker. 1037 // FIXME: The checker should be updated to handle all system calls that take 1038 // 'FILE*' argument. These are now ignored. 1039 if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader()) 1040 return State; 1041 1042 for (SymbolRef Sym : Escaped) { 1043 // The symbol escaped. 1044 // From now the stream can be manipulated in unknown way to the checker, 1045 // it is not possible to handle it any more. 1046 // Optimistically, assume that the corresponding file handle will be closed 1047 // somewhere else. 1048 // Remove symbol from state so the following stream calls on this symbol are 1049 // not handled by the checker. 1050 State = State->remove<StreamMap>(Sym); 1051 } 1052 return State; 1053 } 1054 1055 void ento::registerStreamChecker(CheckerManager &Mgr) { 1056 Mgr.registerChecker<StreamChecker>(); 1057 } 1058 1059 bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) { 1060 return true; 1061 } 1062 1063 void ento::registerStreamTesterChecker(CheckerManager &Mgr) { 1064 auto *Checker = Mgr.getChecker<StreamChecker>(); 1065 Checker->TestMode = true; 1066 } 1067 1068 bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) { 1069 return true; 1070 } 1071