summaryrefslogtreecommitdiffstats
path: root/gnu/llvm/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
diff options
context:
space:
mode:
authorpatrick <patrick@openbsd.org>2020-08-03 14:31:31 +0000
committerpatrick <patrick@openbsd.org>2020-08-03 14:31:31 +0000
commite5dd70708596ae51455a0ffa086a00c5b29f8583 (patch)
tree5d676f27b570bacf71e786c3b5cff3e6f6679b59 /gnu/llvm/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
parentImport LLVM 10.0.0 release including clang, lld and lldb. (diff)
downloadwireguard-openbsd-e5dd70708596ae51455a0ffa086a00c5b29f8583.tar.xz
wireguard-openbsd-e5dd70708596ae51455a0ffa086a00c5b29f8583.zip
Import LLVM 10.0.0 release including clang, lld and lldb.
ok hackroom tested by plenty
Diffstat (limited to 'gnu/llvm/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp')
-rw-r--r--gnu/llvm/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp356
1 files changed, 356 insertions, 0 deletions
diff --git a/gnu/llvm/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/gnu/llvm/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
new file mode 100644
index 00000000000..47099f2afb6
--- /dev/null
+++ b/gnu/llvm/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -0,0 +1,356 @@
+//===-- StreamChecker.cpp -----------------------------------------*- C++ -*--//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines checkers that model and check stream handling functions.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
+#include <functional>
+
+using namespace clang;
+using namespace ento;
+using namespace std::placeholders;
+
+namespace {
+
+struct StreamState {
+ enum Kind { Opened, Closed, OpenFailed, Escaped } K;
+
+ StreamState(Kind k) : K(k) {}
+
+ bool isOpened() const { return K == Opened; }
+ bool isClosed() const { return K == Closed; }
+ //bool isOpenFailed() const { return K == OpenFailed; }
+ //bool isEscaped() const { return K == Escaped; }
+
+ bool operator==(const StreamState &X) const { return K == X.K; }
+
+ static StreamState getOpened() { return StreamState(Opened); }
+ static StreamState getClosed() { return StreamState(Closed); }
+ static StreamState getOpenFailed() { return StreamState(OpenFailed); }
+ static StreamState getEscaped() { return StreamState(Escaped); }
+
+ void Profile(llvm::FoldingSetNodeID &ID) const {
+ ID.AddInteger(K);
+ }
+};
+
+class StreamChecker : public Checker<eval::Call,
+ check::DeadSymbols > {
+ mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence,
+ BT_doubleclose, BT_ResourceLeak;
+
+public:
+ bool evalCall(const CallEvent &Call, CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
+
+private:
+ using FnCheck = std::function<void(const StreamChecker *, const CallEvent &,
+ CheckerContext &)>;
+
+ CallDescriptionMap<FnCheck> Callbacks = {
+ {{"fopen"}, &StreamChecker::evalFopen},
+ {{"freopen", 3}, &StreamChecker::evalFreopen},
+ {{"tmpfile"}, &StreamChecker::evalFopen},
+ {{"fclose", 1}, &StreamChecker::evalFclose},
+ {{"fread", 4},
+ std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)},
+ {{"fwrite", 4},
+ std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)},
+ {{"fseek", 3}, &StreamChecker::evalFseek},
+ {{"ftell", 1},
+ std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
+ {{"rewind", 1},
+ std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
+ {{"fgetpos", 2},
+ std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
+ {{"fsetpos", 2},
+ std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
+ {{"clearerr", 1},
+ std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
+ {{"feof", 1},
+ std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
+ {{"ferror", 1},
+ std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
+ {{"fileno", 1},
+ std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)},
+ };
+
+ void evalFopen(const CallEvent &Call, CheckerContext &C) const;
+ void evalFreopen(const CallEvent &Call, CheckerContext &C) const;
+ void evalFclose(const CallEvent &Call, CheckerContext &C) const;
+ void evalFseek(const CallEvent &Call, CheckerContext &C) const;
+
+ void checkArgNullStream(const CallEvent &Call, CheckerContext &C,
+ unsigned ArgI) const;
+ bool checkNullStream(SVal SV, CheckerContext &C,
+ ProgramStateRef &State) const;
+ void checkFseekWhence(SVal SV, CheckerContext &C,
+ ProgramStateRef &State) const;
+ bool checkDoubleClose(const CallEvent &Call, CheckerContext &C,
+ ProgramStateRef &State) const;
+};
+
+} // end anonymous namespace
+
+REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState)
+
+
+bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const {
+ const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
+ if (!FD || FD->getKind() != Decl::Function)
+ return false;
+
+ // Recognize "global C functions" with only integral or pointer arguments
+ // (and matching name) as stream functions.
+ if (!Call.isGlobalCFunction())
+ return false;
+ for (auto P : Call.parameters()) {
+ QualType T = P->getType();
+ if (!T->isIntegralOrEnumerationType() && !T->isPointerType())
+ return false;
+ }
+
+ const FnCheck *Callback = Callbacks.lookup(Call);
+ if (!Callback)
+ return false;
+
+ (*Callback)(this, Call, C);
+
+ return C.isDifferent();
+}
+
+void StreamChecker::evalFopen(const CallEvent &Call, CheckerContext &C) const {
+ ProgramStateRef state = C.getState();
+ SValBuilder &svalBuilder = C.getSValBuilder();
+ const LocationContext *LCtx = C.getPredecessor()->getLocationContext();
+ auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
+ if (!CE)
+ return;
+
+ DefinedSVal RetVal =
+ svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount())
+ .castAs<DefinedSVal>();
+ state = state->BindExpr(CE, C.getLocationContext(), RetVal);
+
+ ConstraintManager &CM = C.getConstraintManager();
+ // Bifurcate the state into two: one with a valid FILE* pointer, the other
+ // with a NULL.
+ ProgramStateRef stateNotNull, stateNull;
+ std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal);
+
+ SymbolRef Sym = RetVal.getAsSymbol();
+ assert(Sym && "RetVal must be a symbol here.");
+ stateNotNull = stateNotNull->set<StreamMap>(Sym, StreamState::getOpened());
+ stateNull = stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed());
+
+ C.addTransition(stateNotNull);
+ C.addTransition(stateNull);
+}
+
+void StreamChecker::evalFreopen(const CallEvent &Call,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr());
+ if (!CE)
+ return;
+
+ Optional<DefinedSVal> StreamVal = Call.getArgSVal(2).getAs<DefinedSVal>();
+ if (!StreamVal)
+ return;
+ // Do not allow NULL as passed stream pointer.
+ // This is not specified in the man page but may crash on some system.
+ checkNullStream(*StreamVal, C, State);
+ // Check if error was generated.
+ if (C.isDifferent())
+ return;
+
+ SymbolRef StreamSym = StreamVal->getAsSymbol();
+ // Do not care about special values for stream ("(FILE *)0x12345"?).
+ if (!StreamSym)
+ return;
+
+ // Generate state for non-failed case.
+ // Return value is the passed stream pointer.
+ // According to the documentations, the stream is closed first
+ // but any close error is ignored. The state changes to (or remains) opened.
+ ProgramStateRef StateRetNotNull =
+ State->BindExpr(CE, C.getLocationContext(), *StreamVal);
+ // Generate state for NULL return value.
+ // Stream switches to OpenFailed state.
+ ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(),
+ C.getSValBuilder().makeNull());
+
+ StateRetNotNull =
+ StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened());
+ StateRetNull =
+ StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed());
+
+ C.addTransition(StateRetNotNull);
+ C.addTransition(StateRetNull);
+}
+
+void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ if (checkDoubleClose(Call, C, State))
+ C.addTransition(State);
+}
+
+void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const {
+ const Expr *AE2 = Call.getArgExpr(2);
+ if (!AE2)
+ return;
+
+ ProgramStateRef State = C.getState();
+
+ bool StateChanged = checkNullStream(Call.getArgSVal(0), C, State);
+ // Check if error was generated.
+ if (C.isDifferent())
+ return;
+
+ // Check the legality of the 'whence' argument of 'fseek'.
+ checkFseekWhence(State->getSVal(AE2, C.getLocationContext()), C, State);
+
+ if (!C.isDifferent() && StateChanged)
+ C.addTransition(State);
+
+ return;
+}
+
+void StreamChecker::checkArgNullStream(const CallEvent &Call, CheckerContext &C,
+ unsigned ArgI) const {
+ ProgramStateRef State = C.getState();
+ if (checkNullStream(Call.getArgSVal(ArgI), C, State))
+ C.addTransition(State);
+}
+
+bool StreamChecker::checkNullStream(SVal SV, CheckerContext &C,
+ ProgramStateRef &State) const {
+ Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>();
+ if (!DV)
+ return false;
+
+ ConstraintManager &CM = C.getConstraintManager();
+ ProgramStateRef StateNotNull, StateNull;
+ std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *DV);
+
+ if (!StateNotNull && StateNull) {
+ if (ExplodedNode *N = C.generateErrorNode(StateNull)) {
+ if (!BT_nullfp)
+ BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer",
+ "Stream pointer might be NULL."));
+ C.emitReport(std::make_unique<PathSensitiveBugReport>(
+ *BT_nullfp, BT_nullfp->getDescription(), N));
+ }
+ return false;
+ }
+
+ if (StateNotNull) {
+ State = StateNotNull;
+ return true;
+ }
+
+ return false;
+}
+
+void StreamChecker::checkFseekWhence(SVal SV, CheckerContext &C,
+ ProgramStateRef &State) const {
+ Optional<nonloc::ConcreteInt> CI = SV.getAs<nonloc::ConcreteInt>();
+ if (!CI)
+ return;
+
+ int64_t X = CI->getValue().getSExtValue();
+ if (X >= 0 && X <= 2)
+ return;
+
+ if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) {
+ if (!BT_illegalwhence)
+ BT_illegalwhence.reset(
+ new BuiltinBug(this, "Illegal whence argument",
+ "The whence argument to fseek() should be "
+ "SEEK_SET, SEEK_END, or SEEK_CUR."));
+ C.emitReport(std::make_unique<PathSensitiveBugReport>(
+ *BT_illegalwhence, BT_illegalwhence->getDescription(), N));
+ }
+}
+
+bool StreamChecker::checkDoubleClose(const CallEvent &Call, CheckerContext &C,
+ ProgramStateRef &State) const {
+ SymbolRef Sym = Call.getArgSVal(0).getAsSymbol();
+ if (!Sym)
+ return false;
+
+ const StreamState *SS = State->get<StreamMap>(Sym);
+
+ // If the file stream is not tracked, return.
+ if (!SS)
+ return false;
+
+ // Check: Double close a File Descriptor could cause undefined behaviour.
+ // Conforming to man-pages
+ if (SS->isClosed()) {
+ ExplodedNode *N = C.generateErrorNode();
+ if (N) {
+ if (!BT_doubleclose)
+ BT_doubleclose.reset(new BuiltinBug(
+ this, "Double fclose", "Try to close a file Descriptor already"
+ " closed. Cause undefined behaviour."));
+ C.emitReport(std::make_unique<PathSensitiveBugReport>(
+ *BT_doubleclose, BT_doubleclose->getDescription(), N));
+ }
+ return false;
+ }
+
+ // Close the File Descriptor.
+ State = State->set<StreamMap>(Sym, StreamState::getClosed());
+
+ return true;
+}
+
+void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ // TODO: Clean up the state.
+ const StreamMapTy &Map = State->get<StreamMap>();
+ for (const auto &I: Map) {
+ SymbolRef Sym = I.first;
+ const StreamState &SS = I.second;
+ if (!SymReaper.isDead(Sym) || !SS.isOpened())
+ continue;
+
+ ExplodedNode *N = C.generateErrorNode();
+ if (!N)
+ continue;
+
+ if (!BT_ResourceLeak)
+ BT_ResourceLeak.reset(
+ new BuiltinBug(this, "Resource Leak",
+ "Opened File never closed. Potential Resource leak."));
+ C.emitReport(std::make_unique<PathSensitiveBugReport>(
+ *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N));
+ }
+}
+
+void ento::registerStreamChecker(CheckerManager &mgr) {
+ mgr.registerChecker<StreamChecker>();
+}
+
+bool ento::shouldRegisterStreamChecker(const LangOptions &LO) {
+ return true;
+}