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_doubleclose, 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::preFclose, &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 preFclose(const FnDescription *Desc, const CallEvent &Call, 114 CheckerContext &C) const; 115 void evalFclose(const FnDescription *Desc, const CallEvent &Call, 116 CheckerContext &C) const; 117 118 void preFseek(const FnDescription *Desc, const CallEvent &Call, 119 CheckerContext &C) const; 120 121 void preDefault(const FnDescription *Desc, const CallEvent &Call, 122 CheckerContext &C) const; 123 124 /// Check that the stream (in StreamVal) is not NULL. 125 /// If it can only be NULL a fatal error is emitted and nullptr returned. 126 /// Otherwise the return value is a new state where the stream is constrained 127 /// to be non-null. 128 ProgramStateRef ensureStreamNonNull(SVal StreamVal, CheckerContext &C, 129 ProgramStateRef State) const; 130 131 /// Check that the stream is not closed. 132 /// Return a state where the stream is guaranteed to not in closed state 133 /// (if data about it exists). 134 /// Generate error if the stream is provable in closed state. 135 ProgramStateRef ensureStreamNotClosed(SVal StreamVal, CheckerContext &C, 136 ProgramStateRef State) const; 137 138 /// Check the legality of the 'whence' argument of 'fseek'. 139 /// Generate error and return nullptr if it is found to be illegal. 140 /// Otherwise returns the state. 141 /// (State is not changed here because the "whence" value is already known.) 142 ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 143 ProgramStateRef State) const; 144 145 /// Find the description data of the function called by a call event. 146 /// Returns nullptr if no function is recognized. 147 const FnDescription *lookupFn(const CallEvent &Call) const { 148 // Recognize "global C functions" with only integral or pointer arguments 149 // (and matching name) as stream functions. 150 if (!Call.isGlobalCFunction()) 151 return nullptr; 152 for (auto P : Call.parameters()) { 153 QualType T = P->getType(); 154 if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) 155 return nullptr; 156 } 157 158 return FnDescriptions.lookup(Call); 159 } 160 }; 161 162 } // end anonymous namespace 163 164 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 165 166 void StreamChecker::checkPreCall(const CallEvent &Call, 167 CheckerContext &C) const { 168 const FnDescription *Desc = lookupFn(Call); 169 if (!Desc || !Desc->PreFn) 170 return; 171 172 Desc->PreFn(this, Desc, Call, C); 173 } 174 175 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { 176 const FnDescription *Desc = lookupFn(Call); 177 if (!Desc || !Desc->EvalFn) 178 return false; 179 180 Desc->EvalFn(this, Desc, Call, C); 181 182 return C.isDifferent(); 183 } 184 185 void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, 186 CheckerContext &C) const { 187 ProgramStateRef State = C.getState(); 188 SValBuilder &SVB = C.getSValBuilder(); 189 const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); 190 191 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 192 if (!CE) 193 return; 194 195 DefinedSVal RetVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) 196 .castAs<DefinedSVal>(); 197 SymbolRef RetSym = RetVal.getAsSymbol(); 198 assert(RetSym && "RetVal must be a symbol here."); 199 200 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 201 202 // Bifurcate the state into two: one with a valid FILE* pointer, the other 203 // with a NULL. 204 ProgramStateRef StateNotNull, StateNull; 205 std::tie(StateNotNull, StateNull) = 206 C.getConstraintManager().assumeDual(State, RetVal); 207 208 StateNotNull = StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened()); 209 StateNull = StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed()); 210 211 C.addTransition(StateNotNull); 212 C.addTransition(StateNull); 213 } 214 215 void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, 216 CheckerContext &C) const { 217 // Do not allow NULL as passed stream pointer but allow a closed stream. 218 ProgramStateRef State = C.getState(); 219 State = ensureStreamNonNull(getStreamArg(Desc, Call), C, State); 220 if (!State) 221 return; 222 223 C.addTransition(State); 224 } 225 226 void StreamChecker::evalFreopen(const FnDescription *Desc, 227 const CallEvent &Call, 228 CheckerContext &C) const { 229 ProgramStateRef State = C.getState(); 230 231 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 232 if (!CE) 233 return; 234 235 Optional<DefinedSVal> StreamVal = 236 getStreamArg(Desc, Call).getAs<DefinedSVal>(); 237 if (!StreamVal) 238 return; 239 240 SymbolRef StreamSym = StreamVal->getAsSymbol(); 241 // Do not care about concrete values for stream ("(FILE *)0x12345"?). 242 // FIXME: Are stdin, stdout, stderr such values? 243 if (!StreamSym) 244 return; 245 246 // Generate state for non-failed case. 247 // Return value is the passed stream pointer. 248 // According to the documentations, the stream is closed first 249 // but any close error is ignored. The state changes to (or remains) opened. 250 ProgramStateRef StateRetNotNull = 251 State->BindExpr(CE, C.getLocationContext(), *StreamVal); 252 // Generate state for NULL return value. 253 // Stream switches to OpenFailed state. 254 ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(), 255 C.getSValBuilder().makeNull()); 256 257 StateRetNotNull = 258 StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened()); 259 StateRetNull = 260 StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed()); 261 262 C.addTransition(StateRetNotNull); 263 C.addTransition(StateRetNull); 264 } 265 266 void StreamChecker::preFclose(const FnDescription *Desc, const CallEvent &Call, 267 CheckerContext &C) const { 268 ProgramStateRef State = C.getState(); 269 State = ensureStreamNotClosed(getStreamArg(Desc, Call), C, State); 270 if (!State) 271 return; 272 273 C.addTransition(State); 274 } 275 276 void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, 277 CheckerContext &C) const { 278 ProgramStateRef State = C.getState(); 279 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); 280 if (!Sym) 281 return; 282 283 const StreamState *SS = State->get<StreamMap>(Sym); 284 if (!SS) 285 return; 286 287 // Close the File Descriptor. 288 // Regardless if the close fails or not, stream becomes "closed" 289 // and can not be used any more. 290 State = State->set<StreamMap>(Sym, StreamState::getClosed()); 291 292 C.addTransition(State); 293 } 294 295 void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, 296 CheckerContext &C) const { 297 ProgramStateRef State = C.getState(); 298 SVal StreamVal = getStreamArg(Desc, Call); 299 State = ensureStreamNonNull(StreamVal, C, State); 300 if (!State) 301 return; 302 State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State); 303 if (!State) 304 return; 305 306 C.addTransition(State); 307 } 308 309 void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call, 310 CheckerContext &C) const { 311 ProgramStateRef State = C.getState(); 312 SVal StreamVal = getStreamArg(Desc, Call); 313 State = ensureStreamNonNull(StreamVal, C, State); 314 if (!State) 315 return; 316 317 C.addTransition(State); 318 } 319 320 ProgramStateRef 321 StreamChecker::ensureStreamNonNull(SVal StreamVal, CheckerContext &C, 322 ProgramStateRef State) const { 323 auto Stream = StreamVal.getAs<DefinedSVal>(); 324 if (!Stream) 325 return State; 326 327 ConstraintManager &CM = C.getConstraintManager(); 328 329 ProgramStateRef StateNotNull, StateNull; 330 std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *Stream); 331 332 if (!StateNotNull && StateNull) { 333 if (ExplodedNode *N = C.generateErrorNode(StateNull)) { 334 if (!BT_nullfp) 335 BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", 336 "Stream pointer might be NULL.")); 337 C.emitReport(std::make_unique<PathSensitiveBugReport>( 338 *BT_nullfp, BT_nullfp->getDescription(), N)); 339 } 340 return nullptr; 341 } 342 343 return StateNotNull; 344 } 345 346 ProgramStateRef 347 StreamChecker::ensureStreamNotClosed(SVal StreamVal, CheckerContext &C, 348 ProgramStateRef State) const { 349 SymbolRef Sym = StreamVal.getAsSymbol(); 350 if (!Sym) 351 return State; 352 353 const StreamState *SS = State->get<StreamMap>(Sym); 354 if (!SS) 355 return State; 356 357 // Check: Double close a File Descriptor could cause undefined behaviour. 358 // Conforming to man-pages 359 if (SS->isClosed()) { 360 ExplodedNode *N = C.generateErrorNode(); 361 if (N) { 362 if (!BT_doubleclose) 363 BT_doubleclose.reset(new BuiltinBug( 364 this, "Double fclose", "Try to close a file Descriptor already" 365 " closed. Cause undefined behaviour.")); 366 C.emitReport(std::make_unique<PathSensitiveBugReport>( 367 *BT_doubleclose, BT_doubleclose->getDescription(), N)); 368 return nullptr; 369 } 370 371 return State; 372 } 373 374 return State; 375 } 376 377 ProgramStateRef 378 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 379 ProgramStateRef State) const { 380 Optional<nonloc::ConcreteInt> CI = WhenceVal.getAs<nonloc::ConcreteInt>(); 381 if (!CI) 382 return State; 383 384 int64_t X = CI->getValue().getSExtValue(); 385 if (X >= 0 && X <= 2) 386 return State; 387 388 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 389 if (!BT_illegalwhence) 390 BT_illegalwhence.reset( 391 new BuiltinBug(this, "Illegal whence argument", 392 "The whence argument to fseek() should be " 393 "SEEK_SET, SEEK_END, or SEEK_CUR.")); 394 C.emitReport(std::make_unique<PathSensitiveBugReport>( 395 *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); 396 return nullptr; 397 } 398 399 return State; 400 } 401 402 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 403 CheckerContext &C) const { 404 ProgramStateRef State = C.getState(); 405 406 // TODO: Clean up the state. 407 const StreamMapTy &Map = State->get<StreamMap>(); 408 for (const auto &I : Map) { 409 SymbolRef Sym = I.first; 410 const StreamState &SS = I.second; 411 if (!SymReaper.isDead(Sym) || !SS.isOpened()) 412 continue; 413 414 ExplodedNode *N = C.generateErrorNode(); 415 if (!N) 416 continue; 417 418 if (!BT_ResourceLeak) 419 BT_ResourceLeak.reset( 420 new BuiltinBug(this, "Resource Leak", 421 "Opened File never closed. Potential Resource leak.")); 422 C.emitReport(std::make_unique<PathSensitiveBugReport>( 423 *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); 424 } 425 } 426 427 void ento::registerStreamChecker(CheckerManager &mgr) { 428 mgr.registerChecker<StreamChecker>(); 429 } 430 431 bool ento::shouldRegisterStreamChecker(const LangOptions &LO) { return true; } 432