Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1431,4 +1431,8 @@ Dependencies<[PthreadLockBase]>, Documentation; +def FuchsiaHandleChecker : Checker<"FuchsiaHandleChecker">, + HelpText<"A Checker that detect leaks related to Fuchsia handles">, + Documentation; + } // end fuchsia Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h @@ -213,6 +213,22 @@ return addTransition(State, (Tag ? Tag : Location.getTag())); } + /// Generate a transition to a node that will be used to report + /// an error. This node will not be a sink. That is, exploration will + /// continue along this path. + /// + /// @param State The state of the generated node. + /// @param Pred The transition will be generated from the specified Pred node + /// to the newly generated node. + /// @param Tag The tag to uniquely identify the creation site. If null, + /// the default tag for the checker will be used. + ExplodedNode * + generateNonFatalErrorNode(ProgramStateRef State, + ExplodedNode *Pred, + const ProgramPointTag *Tag = nullptr) { + return addTransition(State, Pred, (Tag ? Tag : Location.getTag())); + } + /// Emit the diagnostics report. void emitReport(std::unique_ptr R) { Changed = true; Index: clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -37,6 +37,7 @@ EnumCastOutOfRangeChecker.cpp ExprInspectionChecker.cpp FixedAddressChecker.cpp + FuchsiaHandleChecker.cpp GCDAntipatternChecker.cpp GenericTaintChecker.cpp GTestChecker.cpp Index: clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp =================================================================== --- /dev/null +++ clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp @@ -0,0 +1,469 @@ +//=== 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: +// +// +-----------------------------------------------------------+ +// | | +// | release_func failed | +// | +---------+ | +// | | | | +// | | | | +// | +-+---------v-+ | +// | acquire_func succeeded | | Escape | +// | +-----------------> Allocated +-------------------+ +// | | | | +// | | +-----+------++ +// | | | | +// | | release_func | | +// | | succeeded | +--+ +// | | | | handle +--------+ +// | | +----v-----+ | dies | | +// | | Escape | | +----------> Leaked | +// +--v-------+--+ +------------+ Released | |(REPORT)| +// | | | | | +--------+ +// | 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 states MaybeReleased and +// 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)) |handle will be acquired +// 2. __attribute__((release_handle)) |handle will be released +// 3. __attribute__((use_handle)) |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)) , +// zx_handle_t* out1 __attribute__((acquire_handle))); +// denotes a syscall which will acquire two handles and save them to 'out0' and +// 'out1' when succeeded. +// +//===----------------------------------------------------------------------===// + +#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" + +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, MaybeReleased, Released } 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 maybeReleased() const { return K == Kind::MaybeReleased; } + + static HandleState getMaybeAllocated(SymbolRef ErrorSym) { + return HandleState(Kind::MaybeAllocated, ErrorSym); + } + static HandleState getAllocated(ProgramStateRef State, HandleState S) { + assert(S.maybeAllocated() || S.maybeReleased()); + assert(State->getConstraintManager() + .isNull(State, S.getErrorSym()) + .isConstrained()); + return HandleState(Kind::Allocated, nullptr); + } + static HandleState getMaybeReleased(SymbolRef ErrorSym) { + return HandleState(Kind::MaybeReleased, ErrorSym); + } + static HandleState getReleased(ProgramStateRef State, HandleState S) { + assert(S.maybeReleased()); + assert(State->getConstraintManager() + .isNull(State, S.getErrorSym()) + .isConstrained()); + return HandleState(Kind::Released, nullptr); + } + + SymbolRef getErrorSym() const { return ErrorSym; } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(static_cast(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::MaybeReleased) + CASE(Kind::Released) + } + } + + LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); } +}; + +class FuchsiaHandleChecker + : public Checker { + BugType LeakBugType{this, "Fuchsia handle leak", "Fuchsia Handle Error", + /*SuppressOnSink=*/true}; + BugType DoubleReleaseBugType{this, "Fuchsia handle double release", + "Fuchsia Handle Error"}; + BugType UseAfterFreeBugType{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 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()) { + 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 ArgLoc = Arg.getAs()) + return State->getSVal(*ArgLoc).getAsSymbol(); + } + } + return nullptr; +} + +void FuchsiaHandleChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + const FunctionDecl *FuncDecl = dyn_cast_or_null(Call.getDecl()); + if (!FuncDecl) + return; + + ProgramStateRef State = C.getState(); + + for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { + Optional ParamIdx = Call.getAdjustedParameterIndex(Arg); + if (!ParamIdx) + continue; + + const ParmVarDecl *PVD = FuncDecl->getParamDecl(*ParamIdx); + SymbolRef HandleSym = + getFuchsiaHandleSymbol(PVD->getType(), Call.getArgSVal(Arg), State); + if (!HandleSym) + continue; + + // Handled in checkPostCall. + if (PVD->hasAttr() || PVD->hasAttr()) + continue; + + const HandleState *HState = State->get(HandleSym); + if (!HState) + continue; + + if (PVD->hasAttr() || PVD->getType()->isIntegerType()) { + if (HState->isReleased() || HState->maybeReleased()) { + reportUseAfterFree(HandleSym, Call.getArgSourceRange(Arg), C); + return; + } + } + if (!PVD->hasAttr() && + PVD->getType()->isIntegerType()) { + // Working around integer by-value escapes. + State = State->remove(HandleSym); + } + } + C.addTransition(State); +} + +void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const FunctionDecl *FuncDecl = dyn_cast_or_null(Call.getDecl()); + if (!FuncDecl) + return; + + ProgramStateRef State = C.getState(); + + SymbolRef ResultSymbol = nullptr; + if (const auto *TypeDefTy = FuncDecl->getReturnType()->getAs()) + if (TypeDefTy->getDecl()->getName() == ErrorTypeName) + ResultSymbol = Call.getReturnValue().getAsSymbol(); + + // Function returns an open handle. + if (FuncDecl->hasAttr()) { + SymbolRef RetSym = Call.getReturnValue().getAsSymbol(); + State = + State->set(RetSym, HandleState::getMaybeAllocated(nullptr)); + } + + for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { + Optional ParamIdx = Call.getAdjustedParameterIndex(Arg); + if (!ParamIdx) + continue; + + const ParmVarDecl *PVD = FuncDecl->getParamDecl(*ParamIdx); + SymbolRef HandleSym = + getFuchsiaHandleSymbol(PVD->getType(), Call.getArgSVal(Arg), State); + if (!HandleSym) + continue; + + const HandleState *HState = State->get(HandleSym); + if (PVD->hasAttr()) { + if (HState && (HState->isReleased() || HState->maybeReleased())) { + reportDoubleRelease(HandleSym, Call.getArgSourceRange(Arg), C); + return; + } else + State = State->set( + HandleSym, HandleState::getMaybeReleased(ResultSymbol)); + } else if (PVD->hasAttr()) { + State = State->set( + HandleSym, HandleState::getMaybeAllocated(ResultSymbol)); + } + } + C.addTransition(State); +} + +void FuchsiaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SmallVector LeakedSyms; + HStateMapTy TrackedHandles = State->get(); + 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(CurItem.first); + } + + ExplodedNode *N = C.getPredecessor(); + if (!LeakedSyms.empty()) + N = reportLeaks(LeakedSyms, C, N); + + C.addTransition(State, N); +} + +ProgramStateRef FuchsiaHandleChecker::evalAssume(ProgramStateRef State, + SVal Cond, + bool Assumption) const { + ConstraintManager &Cmr = State->getConstraintManager(); + HStateMapTy TrackedHandles = State->get(); + for (auto &CurItem : TrackedHandles) { + SymbolRef ErrorSym = CurItem.second.getErrorSym(); + if (!ErrorSym) + continue; + ConditionTruthVal ErrorVal = Cmr.isNull(State, ErrorSym); + if (ErrorVal.isConstrainedTrue()) { + // Allocation/release succeeded. + if (CurItem.second.maybeReleased()) + State = State->set( + CurItem.first, HandleState::getReleased(State, CurItem.second)); + else if (CurItem.second.maybeAllocated()) + State = State->set( + CurItem.first, HandleState::getAllocated(State, CurItem.second)); + } else if (ErrorVal.isConstrainedFalse()) { + // Allocation/release failed. + if (CurItem.second.maybeReleased()) + State = State->set( + CurItem.first, HandleState::getAllocated(State, CurItem.second)); + else if (CurItem.second.maybeAllocated()) + State = State->remove(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(Call->getDecl()) : nullptr; + for (const SymbolRef &EscapedSymbol : Escaped) { + if (State->contains(EscapedSymbol)) { + bool IsUnescapedUse = false; + // Not all calls should escape our symbols. + if (FuncDecl && (Kind == PSK_DirectEscapeOnCall || + Kind == PSK_IndirectEscapeOnCall)) { + for (unsigned Arg = 0; Arg < Call->getNumArgs(); ++Arg) { + Optional ParamIdx = Call->getAdjustedParameterIndex(Arg); + if (!ParamIdx) + continue; + + const ParmVarDecl *PVD = FuncDecl->getParamDecl(*ParamIdx); + SymbolRef HandleSym = getFuchsiaHandleSymbol( + PVD->getType(), Call->getArgSVal(Arg), State); + if (!HandleSym) + continue; + if (HandleSym == EscapedSymbol && PVD->hasAttr()) + IsUnescapedUse = true; + } + } + if (!IsUnescapedUse) + State = State->remove(EscapedSymbol); + } + } + return State; +} + +ExplodedNode * +FuchsiaHandleChecker::reportLeaks(ArrayRef 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, UseAfterFreeBugType, + "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(Type, Msg, ErrorNode); + if (Range) + R->addRange(*Range); + R->markInteresting(Sym); + C.emitReport(std::move(R)); +} + +void ento::registerFuchsiaHandleChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +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(); + + 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; + } + } +} \ No newline at end of file Index: clang/test/Analysis/fuchsia_handle.c =================================================================== --- /dev/null +++ clang/test/Analysis/fuchsia_handle.c @@ -0,0 +1,112 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,fuchsia.FuchsiaHandleChecker -verify %s + +typedef __typeof__(sizeof(int)) size_t; +typedef int zx_status_t; +typedef __typeof__(sizeof(int)) zx_handle_t; +typedef unsigned int uint32_t; +#define NULL ((void *)0) + +#if defined(__clang__) +#define XZ_HANDLE_ACQUIRE __attribute__((acquire_handle)) +#define XZ_HANDLE_RELEASE __attribute__((release_handle)) +#define XZ_HANDLE_USE __attribute__((use_handle)) +#else +#define XZ_HANDLE_ACQUIRE +#define XZ_HANDLE_RELEASE +#define XZ_HANDLE_USE +#endif + +zx_status_t zx_channel_create( +uint32_t options, + XZ_HANDLE_ACQUIRE zx_handle_t* out0, + zx_handle_t* out1 XZ_HANDLE_ACQUIRE); + +zx_status_t zx_handle_close( + zx_handle_t handle XZ_HANDLE_RELEASE); + +void escape1(zx_handle_t *in); +void escape2(zx_handle_t in); + +void use1(const zx_handle_t *in XZ_HANDLE_USE); +void use2(zx_handle_t in XZ_HANDLE_USE); + +void checkNoLeak01() { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + zx_handle_close(sa); + zx_handle_close(sb); +} + +void checkNoLeak02() { + zx_handle_t ay[2]; + zx_channel_create(0, &ay[0], &ay[1]); + zx_handle_close(ay[0]); + zx_handle_close(ay[1]); +} + +void checkNoLeak03() { + zx_handle_t ay[2]; + zx_channel_create(0, &ay[0], &ay[1]); + for (int i = 0; i < 2; i++) + zx_handle_close(ay[i]); +} + +zx_handle_t checkNoLeak04() { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + zx_handle_close(sa); + return sb; // no warning +} + +zx_handle_t checkNoLeak05(zx_handle_t *out1) { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + *out1 = sa; + return sb; // no warning +} + +void checkNoLeak06() { + zx_handle_t sa, sb; + if (zx_channel_create(0, &sa, &sb)) + return; + zx_handle_close(sa); + zx_handle_close(sb); +} + +void checkNoLeak07(int tag) { + zx_handle_t sa, sb; + if (zx_channel_create(0, &sa, &sb)) + return; + escape1(&sa); + escape2(sb); +} + +void checkNoLeak08(int tag) { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + use1(&sa); + if (tag) + zx_handle_close(sa); + use2(sb); // expected-warning {{Potential leak of handle}} + zx_handle_close(sb); +} + +void checkDoubleRelease01(int tag) { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + if (tag) + zx_handle_close(sa); + zx_handle_close(sa); // expected-warning {{Releasing a previously released handle}} + zx_handle_close(sb); +} + +void checkUseAfterFree01(int tag) { + zx_handle_t sa, sb; + zx_channel_create(0, &sa, &sb); + if (tag) { + zx_handle_close(sa); + use1(&sa); // expected-warning {{Using a previously released handle}} + } + zx_handle_close(sb); + use2(sb); // expected-warning {{Using a previously released handle}} +}