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 struct FnDescription; 54 using FnCheck = std::function<void(const StreamChecker *, const FnDescription *, 55 const CallEvent &, CheckerContext &)>; 56 57 using ArgNoTy = unsigned int; 58 static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max(); 59 60 struct FnDescription { 61 FnCheck PreFn; 62 FnCheck EvalFn; 63 ArgNoTy StreamArgNo; 64 }; 65 66 /// Get the value of the stream argument out of the passed call event. 67 /// The call should contain a function that is described by Desc. 68 SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) { 69 assert(Desc && Desc->StreamArgNo != ArgNone && 70 "Try to get a non-existing stream argument."); 71 return Call.getArgSVal(Desc->StreamArgNo); 72 } 73 74 class StreamChecker 75 : public Checker<check::PreCall, eval::Call, check::DeadSymbols> { 76 mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence, 77 BT_UseAfterClose, BT_UseAfterOpenFailed, BT_ResourceLeak; 78 79 public: 80 void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 81 bool evalCall(const CallEvent &Call, CheckerContext &C) const; 82 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 83 84 private: 85 CallDescriptionMap<FnDescription> FnDescriptions = { 86 {{"fopen"}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, 87 {{"freopen", 3}, 88 {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}}, 89 {{"tmpfile"}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, 90 {{"fclose", 1}, 91 {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}}, 92 {{"fread", 4}, {&StreamChecker::preDefault, nullptr, 3}}, 93 {{"fwrite", 4}, {&StreamChecker::preDefault, nullptr, 3}}, 94 {{"fseek", 3}, {&StreamChecker::preFseek, nullptr, 0}}, 95 {{"ftell", 1}, {&StreamChecker::preDefault, nullptr, 0}}, 96 {{"rewind", 1}, {&StreamChecker::preDefault, nullptr, 0}}, 97 {{"fgetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}}, 98 {{"fsetpos", 2}, {&StreamChecker::preDefault, nullptr, 0}}, 99 {{"clearerr", 1}, {&StreamChecker::preDefault, nullptr, 0}}, 100 {{"feof", 1}, {&StreamChecker::preDefault, nullptr, 0}}, 101 {{"ferror", 1}, {&StreamChecker::preDefault, nullptr, 0}}, 102 {{"fileno", 1}, {&StreamChecker::preDefault, nullptr, 0}}, 103 }; 104 105 void evalFopen(const FnDescription *Desc, const CallEvent &Call, 106 CheckerContext &C) const; 107 108 void preFreopen(const FnDescription *Desc, const CallEvent &Call, 109 CheckerContext &C) const; 110 void evalFreopen(const FnDescription *Desc, const CallEvent &Call, 111 CheckerContext &C) const; 112 113 void evalFclose(const FnDescription *Desc, const CallEvent &Call, 114 CheckerContext &C) const; 115 116 void preFseek(const FnDescription *Desc, const CallEvent &Call, 117 CheckerContext &C) const; 118 119 void preDefault(const FnDescription *Desc, const CallEvent &Call, 120 CheckerContext &C) const; 121 122 /// Check that the stream (in StreamVal) is not NULL. 123 /// If it can only be NULL a fatal error is emitted and nullptr returned. 124 /// Otherwise the return value is a new state where the stream is constrained 125 /// to be non-null. 126 ProgramStateRef ensureStreamNonNull(SVal StreamVal, CheckerContext &C, 127 ProgramStateRef State) const; 128 129 /// Check that the stream is the opened state. 130 /// If the stream is known to be not opened an error is generated 131 /// and nullptr returned, otherwise the original state is returned. 132 ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C, 133 ProgramStateRef State) const; 134 135 /// Check the legality of the 'whence' argument of 'fseek'. 136 /// Generate error and return nullptr if it is found to be illegal. 137 /// Otherwise returns the state. 138 /// (State is not changed here because the "whence" value is already known.) 139 ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 140 ProgramStateRef State) const; 141 142 /// Find the description data of the function called by a call event. 143 /// Returns nullptr if no function is recognized. 144 const FnDescription *lookupFn(const CallEvent &Call) const { 145 // Recognize "global C functions" with only integral or pointer arguments 146 // (and matching name) as stream functions. 147 if (!Call.isGlobalCFunction()) 148 return nullptr; 149 for (auto P : Call.parameters()) { 150 QualType T = P->getType(); 151 if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) 152 return nullptr; 153 } 154 155 return FnDescriptions.lookup(Call); 156 } 157 }; 158 159 } // end anonymous namespace 160 161 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 162 163 void StreamChecker::checkPreCall(const CallEvent &Call, 164 CheckerContext &C) const { 165 const FnDescription *Desc = lookupFn(Call); 166 if (!Desc || !Desc->PreFn) 167 return; 168 169 Desc->PreFn(this, Desc, Call, C); 170 } 171 172 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { 173 const FnDescription *Desc = lookupFn(Call); 174 if (!Desc || !Desc->EvalFn) 175 return false; 176 177 Desc->EvalFn(this, Desc, Call, C); 178 179 return C.isDifferent(); 180 } 181 182 void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, 183 CheckerContext &C) const { 184 ProgramStateRef State = C.getState(); 185 SValBuilder &SVB = C.getSValBuilder(); 186 const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); 187 188 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 189 if (!CE) 190 return; 191 192 DefinedSVal RetVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) 193 .castAs<DefinedSVal>(); 194 SymbolRef RetSym = RetVal.getAsSymbol(); 195 assert(RetSym && "RetVal must be a symbol here."); 196 197 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 198 199 // Bifurcate the state into two: one with a valid FILE* pointer, the other 200 // with a NULL. 201 ProgramStateRef StateNotNull, StateNull; 202 std::tie(StateNotNull, StateNull) = 203 C.getConstraintManager().assumeDual(State, RetVal); 204 205 StateNotNull = StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened()); 206 StateNull = StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed()); 207 208 C.addTransition(StateNotNull); 209 C.addTransition(StateNull); 210 } 211 212 void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, 213 CheckerContext &C) const { 214 // Do not allow NULL as passed stream pointer but allow a closed stream. 215 ProgramStateRef State = C.getState(); 216 State = ensureStreamNonNull(getStreamArg(Desc, Call), C, State); 217 if (!State) 218 return; 219 220 C.addTransition(State); 221 } 222 223 void StreamChecker::evalFreopen(const FnDescription *Desc, 224 const CallEvent &Call, 225 CheckerContext &C) const { 226 ProgramStateRef State = C.getState(); 227 228 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 229 if (!CE) 230 return; 231 232 Optional<DefinedSVal> StreamVal = 233 getStreamArg(Desc, Call).getAs<DefinedSVal>(); 234 if (!StreamVal) 235 return; 236 237 SymbolRef StreamSym = StreamVal->getAsSymbol(); 238 // Do not care about concrete values for stream ("(FILE *)0x12345"?). 239 // FIXME: Are stdin, stdout, stderr such values? 240 if (!StreamSym) 241 return; 242 243 // Generate state for non-failed case. 244 // Return value is the passed stream pointer. 245 // According to the documentations, the stream is closed first 246 // but any close error is ignored. The state changes to (or remains) opened. 247 ProgramStateRef StateRetNotNull = 248 State->BindExpr(CE, C.getLocationContext(), *StreamVal); 249 // Generate state for NULL return value. 250 // Stream switches to OpenFailed state. 251 ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(), 252 C.getSValBuilder().makeNull()); 253 254 StateRetNotNull = 255 StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened()); 256 StateRetNull = 257 StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed()); 258 259 C.addTransition(StateRetNotNull); 260 C.addTransition(StateRetNull); 261 } 262 263 void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, 264 CheckerContext &C) const { 265 ProgramStateRef State = C.getState(); 266 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); 267 if (!Sym) 268 return; 269 270 const StreamState *SS = State->get<StreamMap>(Sym); 271 if (!SS) 272 return; 273 274 // Close the File Descriptor. 275 // Regardless if the close fails or not, stream becomes "closed" 276 // and can not be used any more. 277 State = State->set<StreamMap>(Sym, StreamState::getClosed()); 278 279 C.addTransition(State); 280 } 281 282 void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, 283 CheckerContext &C) const { 284 ProgramStateRef State = C.getState(); 285 SVal StreamVal = getStreamArg(Desc, Call); 286 State = ensureStreamNonNull(StreamVal, C, State); 287 if (!State) 288 return; 289 State = ensureStreamOpened(StreamVal, C, State); 290 if (!State) 291 return; 292 State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State); 293 if (!State) 294 return; 295 296 C.addTransition(State); 297 } 298 299 void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call, 300 CheckerContext &C) const { 301 ProgramStateRef State = C.getState(); 302 SVal StreamVal = getStreamArg(Desc, Call); 303 State = ensureStreamNonNull(StreamVal, C, State); 304 if (!State) 305 return; 306 State = ensureStreamOpened(StreamVal, C, State); 307 if (!State) 308 return; 309 310 C.addTransition(State); 311 } 312 313 ProgramStateRef 314 StreamChecker::ensureStreamNonNull(SVal StreamVal, CheckerContext &C, 315 ProgramStateRef State) const { 316 auto Stream = StreamVal.getAs<DefinedSVal>(); 317 if (!Stream) 318 return State; 319 320 ConstraintManager &CM = C.getConstraintManager(); 321 322 ProgramStateRef StateNotNull, StateNull; 323 std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *Stream); 324 325 if (!StateNotNull && StateNull) { 326 if (ExplodedNode *N = C.generateErrorNode(StateNull)) { 327 if (!BT_nullfp) 328 BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", 329 "Stream pointer might be NULL.")); 330 C.emitReport(std::make_unique<PathSensitiveBugReport>( 331 *BT_nullfp, BT_nullfp->getDescription(), N)); 332 } 333 return nullptr; 334 } 335 336 return StateNotNull; 337 } 338 339 ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal, 340 CheckerContext &C, 341 ProgramStateRef State) const { 342 SymbolRef Sym = StreamVal.getAsSymbol(); 343 if (!Sym) 344 return State; 345 346 const StreamState *SS = State->get<StreamMap>(Sym); 347 if (!SS) 348 return State; 349 350 if (SS->isClosed()) { 351 // Using a stream pointer after 'fclose' causes undefined behavior 352 // according to cppreference.com . 353 ExplodedNode *N = C.generateErrorNode(); 354 if (N) { 355 if (!BT_UseAfterClose) 356 BT_UseAfterClose.reset(new BuiltinBug(this, "Closed stream", 357 "Stream might be already closed. " 358 "Causes undefined behaviour.")); 359 C.emitReport(std::make_unique<PathSensitiveBugReport>( 360 *BT_UseAfterClose, BT_UseAfterClose->getDescription(), N)); 361 return nullptr; 362 } 363 364 return State; 365 } 366 367 if (SS->isOpenFailed()) { 368 // Using a stream that has failed to open is likely to cause problems. 369 // This should usually not occur because stream pointer is NULL. 370 // But freopen can cause a state when stream pointer remains non-null but 371 // failed to open. 372 ExplodedNode *N = C.generateErrorNode(); 373 if (N) { 374 if (!BT_UseAfterOpenFailed) 375 BT_UseAfterOpenFailed.reset( 376 new BuiltinBug(this, "Invalid stream", 377 "Stream might be invalid after " 378 "(re-)opening it has failed. " 379 "Can cause undefined behaviour.")); 380 C.emitReport(std::make_unique<PathSensitiveBugReport>( 381 *BT_UseAfterOpenFailed, BT_UseAfterOpenFailed->getDescription(), N)); 382 return nullptr; 383 } 384 return State; 385 } 386 387 return State; 388 } 389 390 ProgramStateRef 391 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 392 ProgramStateRef State) const { 393 Optional<nonloc::ConcreteInt> CI = WhenceVal.getAs<nonloc::ConcreteInt>(); 394 if (!CI) 395 return State; 396 397 int64_t X = CI->getValue().getSExtValue(); 398 if (X >= 0 && X <= 2) 399 return State; 400 401 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 402 if (!BT_illegalwhence) 403 BT_illegalwhence.reset( 404 new BuiltinBug(this, "Illegal whence argument", 405 "The whence argument to fseek() should be " 406 "SEEK_SET, SEEK_END, or SEEK_CUR.")); 407 C.emitReport(std::make_unique<PathSensitiveBugReport>( 408 *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); 409 return nullptr; 410 } 411 412 return State; 413 } 414 415 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 416 CheckerContext &C) const { 417 ProgramStateRef State = C.getState(); 418 419 // TODO: Clean up the state. 420 const StreamMapTy &Map = State->get<StreamMap>(); 421 for (const auto &I : Map) { 422 SymbolRef Sym = I.first; 423 const StreamState &SS = I.second; 424 if (!SymReaper.isDead(Sym) || !SS.isOpened()) 425 continue; 426 427 ExplodedNode *N = C.generateErrorNode(); 428 if (!N) 429 continue; 430 431 if (!BT_ResourceLeak) 432 BT_ResourceLeak.reset( 433 new BuiltinBug(this, "Resource Leak", 434 "Opened File never closed. Potential Resource leak.")); 435 C.emitReport(std::make_unique<PathSensitiveBugReport>( 436 *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); 437 } 438 } 439 440 void ento::registerStreamChecker(CheckerManager &mgr) { 441 mgr.registerChecker<StreamChecker>(); 442 } 443 444 bool ento::shouldRegisterStreamChecker(const LangOptions &LO) { return true; } 445