diff options
author | Gabor Horvath <xazax@google.com> | 2019-11-15 16:00:46 -0800 |
---|---|---|
committer | Gabor Horvath <xazax@google.com> | 2019-12-20 12:33:16 -0800 |
commit | 82923c71efa57600d015dbc281202941d3d64dde (patch) | |
tree | 80124fceb361f2d5a2847242e4b25bd36fff4530 /clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp | |
parent | 07861e955d0095f25639d84c5726c73b528567cb (diff) | |
download | bcm5719-llvm-82923c71efa57600d015dbc281202941d3d64dde.tar.gz bcm5719-llvm-82923c71efa57600d015dbc281202941d3d64dde.zip |
[analyzer] Add Fuchsia Handle checker
The checker can diagnose handle use after releases, double releases, and
handle leaks.
Differential Revision: https://reviews.llvm.org/D70470
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp')
-rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp new file mode 100644 index 00000000000..25eb4f5ba82 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp @@ -0,0 +1,486 @@ +//=== FuchsiaHandleChecker.cpp - Find handle leaks/double closes -*- 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 checker checks if the handle of Fuchsia is properly used according to +// following rules. +// - If a handle is acquired, it should be released before execution +// ends. +// - If a handle is released, it should not be released again. +// - If a handle is released, it should not be used for other purposes +// such as I/O. +// +// In this checker, each tracked handle is associated with a state. When the +// handle variable is passed to different function calls or syscalls, its state +// changes. The state changes can be generally represented by following ASCII +// Art: +// +// +// +-+---------v-+ +------------+ +// acquire_func succeeded | | Escape | | +// +-----------------> Allocated +---------> Escaped <--+ +// | | | | | | +// | +-----+------++ +------------+ | +// | | | | +// | release_func | +--+ | +// | | | handle +--------+ | +// | | | dies | | | +// | +----v-----+ +---------> Leaked | | +// | | | |(REPORT)| | +// +----------+--+ | Released | Escape +--------+ | +// | | | +---------------------------+ +// | Not tracked <--+ +----+---+-+ +// | | | | | As argument by value +// +------+------+ | release_func | +------+ in function call +// | | | | or by reference in +// | | | | use_func call +// +---------+ +----v-----+ | +-----------+ +// acquire_func failed | Double | +-----> Use after | +// | released | | released | +// | (REPORT) | | (REPORT) | +// +----------+ +-----------+ +// +// acquire_func represents the functions or syscalls that may acquire a handle. +// release_func represents the functions or syscalls that may release a handle. +// use_func represents the functions or syscall that requires an open handle. +// +// If a tracked handle dies in "Released" or "Not Tracked" state, we assume it +// is properly used. Otherwise a bug and will be reported. +// +// Note that, the analyzer does not always know for sure if a function failed +// or succeeded. In those cases we use the state MaybeAllocated. +// Thus, the diagramm above captures the intent, not implementation details. +// +// Due to the fact that the number of handle related syscalls in Fuchsia +// is large, we adopt the annotation attributes to descript syscalls' +// operations(acquire/release/use) on handles instead of hardcoding +// everything in the checker. +// +// We use following annotation attributes for handle related syscalls or +// functions: +// 1. __attribute__((acquire_handle("Fuchsia"))) |handle will be acquired +// 2. __attribute__((release_handle("Fuchsia"))) |handle will be released +// 3. __attribute__((use_handle("Fuchsia"))) |handle will not transit to +// escaped state, it also needs to be open. +// +// For example, an annotated syscall: +// zx_status_t zx_channel_create( +// uint32_t options, +// zx_handle_t* out0 __attribute__((acquire_handle("Fuchsia"))) , +// zx_handle_t* out1 __attribute__((acquire_handle("Fuchsia")))); +// denotes a syscall which will acquire two handles and save them to 'out0' and +// 'out1' when succeeded. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/Attr.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Type.h" +#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/ConstraintManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" + +using namespace clang; +using namespace ento; + +namespace { + +static const StringRef HandleTypeName = "zx_handle_t"; +static const StringRef ErrorTypeName = "zx_status_t"; + +class HandleState { +private: + enum class Kind { MaybeAllocated, Allocated, Released, Escaped } K; + SymbolRef ErrorSym; + HandleState(Kind K, SymbolRef ErrorSym) : K(K), ErrorSym(ErrorSym) {} + +public: + bool operator==(const HandleState &Other) const { + return K == Other.K && ErrorSym == Other.ErrorSym; + } + bool isAllocated() const { return K == Kind::Allocated; } + bool maybeAllocated() const { return K == Kind::MaybeAllocated; } + bool isReleased() const { return K == Kind::Released; } + bool isEscaped() const { return K == Kind::Escaped; } + + static HandleState getMaybeAllocated(SymbolRef ErrorSym) { + return HandleState(Kind::MaybeAllocated, ErrorSym); + } + static HandleState getAllocated(ProgramStateRef State, HandleState S) { + assert(S.maybeAllocated()); + assert(State->getConstraintManager() + .isNull(State, S.getErrorSym()) + .isConstrained()); + return HandleState(Kind::Allocated, nullptr); + } + static HandleState getReleased() { + return HandleState(Kind::Released, nullptr); + } + static HandleState getEscaped() { + return HandleState(Kind::Escaped, nullptr); + } + + SymbolRef getErrorSym() const { return ErrorSym; } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(static_cast<int>(K)); + ID.AddPointer(ErrorSym); + } + + LLVM_DUMP_METHOD void dump(raw_ostream &OS) const { + switch (K) { +#define CASE(ID) \ + case ID: \ + OS << #ID; \ + break; + CASE(Kind::MaybeAllocated) + CASE(Kind::Allocated) + CASE(Kind::Released) + CASE(Kind::Escaped) + } + } + + LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); } +}; + +template <typename Attr> static bool hasFuchsiaAttr(const Decl *D) { + return D->hasAttr<Attr>() && D->getAttr<Attr>()->getHandleType() == "Fuchsia"; +} + +class FuchsiaHandleChecker + : public Checker<check::PostCall, check::PreCall, check::DeadSymbols, + check::PointerEscape, eval::Assume> { + BugType LeakBugType{this, "Fuchsia handle leak", "Fuchsia Handle Error", + /*SuppressOnSink=*/true}; + BugType DoubleReleaseBugType{this, "Fuchsia handle double release", + "Fuchsia Handle Error"}; + BugType UseAfterReleaseBugType{this, "Fuchsia handle use after release", + "Fuchsia Handle Error"}; + +public: + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond, + bool Assumption) const; + ProgramStateRef checkPointerEscape(ProgramStateRef State, + const InvalidatedSymbols &Escaped, + const CallEvent *Call, + PointerEscapeKind Kind) const; + + ExplodedNode *reportLeaks(ArrayRef<SymbolRef> LeakedHandles, + CheckerContext &C, ExplodedNode *Pred) const; + + void reportDoubleRelease(SymbolRef HandleSym, const SourceRange &Range, + CheckerContext &C) const; + + void reportUseAfterFree(SymbolRef HandleSym, const SourceRange &Range, + CheckerContext &C) const; + + void reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, CheckerContext &C, + const SourceRange *Range, const BugType &Type, + StringRef Msg) const; + + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; +}; +} // end anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(HStateMap, SymbolRef, HandleState) + +/// Returns the symbols extracted from the argument or null if it cannot be +/// found. +SymbolRef getFuchsiaHandleSymbol(QualType QT, SVal Arg, ProgramStateRef State) { + int PtrToHandleLevel = 0; + while (QT->isAnyPointerType() || QT->isReferenceType()) { + ++PtrToHandleLevel; + QT = QT->getPointeeType(); + } + if (const auto *HandleType = QT->getAs<TypedefType>()) { + if (HandleType->getDecl()->getName() != HandleTypeName) + return nullptr; + if (PtrToHandleLevel > 1) { + // Not supported yet. + return nullptr; + } + + if (PtrToHandleLevel == 0) { + return Arg.getAsSymbol(); + } else { + assert(PtrToHandleLevel == 1); + if (Optional<Loc> ArgLoc = Arg.getAs<Loc>()) + return State->getSVal(*ArgLoc).getAsSymbol(); + } + } + return nullptr; +} + +void FuchsiaHandleChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!FuncDecl) { + // Unknown call, escape by value handles. They are not covered by + // PointerEscape callback. + for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { + if (SymbolRef Handle = Call.getArgSVal(Arg).getAsSymbol()) + State = State->set<HStateMap>(Handle, HandleState::getEscaped()); + } + C.addTransition(State); + return; + } + + for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { + if (Arg >= FuncDecl->getNumParams()) + break; + const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg); + SymbolRef Handle = + getFuchsiaHandleSymbol(PVD->getType(), Call.getArgSVal(Arg), State); + if (!Handle) + continue; + + // Handled in checkPostCall. + if (hasFuchsiaAttr<ReleaseHandleAttr>(PVD) || + hasFuchsiaAttr<AcquireHandleAttr>(PVD)) + continue; + + const HandleState *HState = State->get<HStateMap>(Handle); + if (!HState || HState->isEscaped()) + continue; + + if (hasFuchsiaAttr<UseHandleAttr>(PVD) || PVD->getType()->isIntegerType()) { + if (HState->isReleased()) { + reportUseAfterFree(Handle, Call.getArgSourceRange(Arg), C); + return; + } + } + if (!hasFuchsiaAttr<UseHandleAttr>(PVD) && + PVD->getType()->isIntegerType()) { + // Working around integer by-value escapes. + State = State->set<HStateMap>(Handle, HandleState::getEscaped()); + } + } + C.addTransition(State); +} + +void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!FuncDecl) + return; + + ProgramStateRef State = C.getState(); + + SymbolRef ResultSymbol = nullptr; + if (const auto *TypeDefTy = FuncDecl->getReturnType()->getAs<TypedefType>()) + if (TypeDefTy->getDecl()->getName() == ErrorTypeName) + ResultSymbol = Call.getReturnValue().getAsSymbol(); + + // Function returns an open handle. + if (hasFuchsiaAttr<AcquireHandleAttr>(FuncDecl)) { + SymbolRef RetSym = Call.getReturnValue().getAsSymbol(); + State = + State->set<HStateMap>(RetSym, HandleState::getMaybeAllocated(nullptr)); + } + + for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { + if (Arg >= FuncDecl->getNumParams()) + break; + const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg); + SymbolRef Handle = + getFuchsiaHandleSymbol(PVD->getType(), Call.getArgSVal(Arg), State); + if (!Handle) + continue; + + const HandleState *HState = State->get<HStateMap>(Handle); + if (HState && HState->isEscaped()) + continue; + if (hasFuchsiaAttr<ReleaseHandleAttr>(PVD)) { + if (HState && HState->isReleased()) { + reportDoubleRelease(Handle, Call.getArgSourceRange(Arg), C); + return; + } else + State = State->set<HStateMap>(Handle, HandleState::getReleased()); + } else if (hasFuchsiaAttr<AcquireHandleAttr>(PVD)) { + State = State->set<HStateMap>( + Handle, HandleState::getMaybeAllocated(ResultSymbol)); + } + } + C.addTransition(State); +} + +void FuchsiaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SmallVector<SymbolRef, 2> LeakedSyms; + HStateMapTy TrackedHandles = State->get<HStateMap>(); + for (auto &CurItem : TrackedHandles) { + if (!SymReaper.isDead(CurItem.first)) + continue; + if (CurItem.second.isAllocated() || CurItem.second.maybeAllocated()) + LeakedSyms.push_back(CurItem.first); + State = State->remove<HStateMap>(CurItem.first); + } + + ExplodedNode *N = C.getPredecessor(); + if (!LeakedSyms.empty()) + N = reportLeaks(LeakedSyms, C, N); + + C.addTransition(State, N); +} + +// Acquiring a handle is not always successful. In Fuchsia most functions +// return a status code that determines the status of the handle. +// When we split the path based on this status code we know that on one +// path we do have the handle and on the other path the acquire failed. +// This method helps avoiding false positive leak warnings on paths where +// the function failed. +// Moreover, when a handle is known to be zero (the invalid handle), +// we no longer can follow the symbol on the path, becaue the constant +// zero will be used instead of the symbol. We also do not need to release +// an invalid handle, so we remove the corresponding symbol from the state. +ProgramStateRef FuchsiaHandleChecker::evalAssume(ProgramStateRef State, + SVal Cond, + bool Assumption) const { + ConstraintManager &Cmr = State->getConstraintManager(); + HStateMapTy TrackedHandles = State->get<HStateMap>(); + for (auto &CurItem : TrackedHandles) { + ConditionTruthVal HandleVal = Cmr.isNull(State, CurItem.first); + if (HandleVal.isConstrainedTrue()) { + // The handle is invalid. We can no longer follow the symbol on this path. + State = State->remove<HStateMap>(CurItem.first); + } + SymbolRef ErrorSym = CurItem.second.getErrorSym(); + if (!ErrorSym) + continue; + ConditionTruthVal ErrorVal = Cmr.isNull(State, ErrorSym); + if (ErrorVal.isConstrainedTrue()) { + // Allocation succeeded. + if (CurItem.second.maybeAllocated()) + State = State->set<HStateMap>( + CurItem.first, HandleState::getAllocated(State, CurItem.second)); + } else if (ErrorVal.isConstrainedFalse()) { + // Allocation failed. + if (CurItem.second.maybeAllocated()) + State = State->remove<HStateMap>(CurItem.first); + } + } + return State; +} + +ProgramStateRef FuchsiaHandleChecker::checkPointerEscape( + ProgramStateRef State, const InvalidatedSymbols &Escaped, + const CallEvent *Call, PointerEscapeKind Kind) const { + const FunctionDecl *FuncDecl = + Call ? dyn_cast_or_null<FunctionDecl>(Call->getDecl()) : nullptr; + + llvm::DenseSet<SymbolRef> UnEscaped; + // Not all calls should escape our symbols. + if (FuncDecl && + (Kind == PSK_DirectEscapeOnCall || Kind == PSK_IndirectEscapeOnCall || + Kind == PSK_EscapeOutParameters)) { + for (unsigned Arg = 0; Arg < Call->getNumArgs(); ++Arg) { + if (Arg >= FuncDecl->getNumParams()) + break; + const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg); + SymbolRef Handle = + getFuchsiaHandleSymbol(PVD->getType(), Call->getArgSVal(Arg), State); + if (!Handle) + continue; + if (hasFuchsiaAttr<UseHandleAttr>(PVD) || + hasFuchsiaAttr<ReleaseHandleAttr>(PVD)) + UnEscaped.insert(Handle); + } + } + + // For out params, we have to deal with derived symbols. See + // MacOSKeychainAPIChecker for details. + for (auto I : State->get<HStateMap>()) { + if (Escaped.count(I.first) && !UnEscaped.count(I.first)) + State = State->set<HStateMap>(I.first, HandleState::getEscaped()); + if (const auto *SD = dyn_cast<SymbolDerived>(I.first)) { + auto ParentSym = SD->getParentSymbol(); + if (Escaped.count(ParentSym)) + State = State->set<HStateMap>(I.first, HandleState::getEscaped()); + } + } + + return State; +} + +ExplodedNode * +FuchsiaHandleChecker::reportLeaks(ArrayRef<SymbolRef> LeakedHandles, + CheckerContext &C, ExplodedNode *Pred) const { + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState(), Pred); + for (SymbolRef LeakedHandle : LeakedHandles) { + reportBug(LeakedHandle, ErrNode, C, nullptr, LeakBugType, + "Potential leak of handle"); + } + return ErrNode; +} + +void FuchsiaHandleChecker::reportDoubleRelease(SymbolRef HandleSym, + const SourceRange &Range, + CheckerContext &C) const { + ExplodedNode *ErrNode = C.generateErrorNode(C.getState()); + reportBug(HandleSym, ErrNode, C, &Range, DoubleReleaseBugType, + "Releasing a previously released handle"); +} + +void FuchsiaHandleChecker::reportUseAfterFree(SymbolRef HandleSym, + const SourceRange &Range, + CheckerContext &C) const { + ExplodedNode *ErrNode = C.generateErrorNode(C.getState()); + reportBug(HandleSym, ErrNode, C, &Range, UseAfterReleaseBugType, + "Using a previously released handle"); +} + +void FuchsiaHandleChecker::reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, + CheckerContext &C, + const SourceRange *Range, + const BugType &Type, StringRef Msg) const { + if (!ErrorNode) + return; + + auto R = std::make_unique<PathSensitiveBugReport>(Type, Msg, ErrorNode); + if (Range) + R->addRange(*Range); + R->markInteresting(Sym); + C.emitReport(std::move(R)); +} + +void ento::registerFuchsiaHandleChecker(CheckerManager &mgr) { + mgr.registerChecker<FuchsiaHandleChecker>(); +} + +bool ento::shouldRegisterFuchsiaHandleChecker(const LangOptions &LO) { + return true; +} + +void FuchsiaHandleChecker::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + + HStateMapTy StateMap = State->get<HStateMap>(); + + if (!StateMap.isEmpty()) { + Out << Sep << "FuchsiaHandleChecker :" << NL; + for (HStateMapTy::iterator I = StateMap.begin(), E = StateMap.end(); I != E; + ++I) { + I.getKey()->dumpToStream(Out); + Out << " : "; + I.getData().dump(Out); + Out << NL; + } + } +} |