//=== 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: // // // +-------------+ +------------+ // acquire_func succeeded | | Escape | | // +-----------------> Allocated +---------> Escaped <--+ // | | | | | | // | +-----+------++ +------------+ | // | | | | // acquire_func | release_func | +--+ | // failed | | | 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 // unowned | +----v-----+ | +-----------+ // acquire_func | | Double | +-----> Use after | // succeeded | | released | | released | // | | (REPORT) | | (REPORT) | // +---------------+ +----------+ +-----------+ // | Allocated | // | Unowned | release_func // | +---------+ // +---------------+ | // | // +-----v----------+ // | Release of | // | unowned handle | // | (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 diagram 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" #include "llvm/ADT/StringExtras.h" #include <optional> usingnamespaceclang; usingnamespaceento; namespace { static const StringRef HandleTypeName = …; static const StringRef ErrorTypeName = …; class HandleState { … }; template <typename Attr> static bool hasFuchsiaAttr(const Decl *D) { … } template <typename Attr> static bool hasFuchsiaUnownedAttr(const Decl *D) { … } class FuchsiaHandleChecker : public Checker<check::PostCall, check::PreCall, check::DeadSymbols, check::PointerEscape, eval::Assume> { … }; } // end anonymous namespace REGISTER_MAP_WITH_PROGRAMSTATE(…) static const ExplodedNode *getAcquireSite(const ExplodedNode *N, SymbolRef Sym, CheckerContext &Ctx) { … } namespace { class FuchsiaHandleSymbolVisitor final : public SymbolVisitor { … }; } // end anonymous namespace /// Returns the symbols extracted from the argument or empty vector if it cannot /// be found. It is unlikely to have over 1024 symbols in one argument. static SmallVector<SymbolRef, 1024> getFuchsiaHandleSymbols(QualType QT, SVal Arg, ProgramStateRef State) { … } void FuchsiaHandleChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { … } void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { … } void FuchsiaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { … } // 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 { … } ProgramStateRef FuchsiaHandleChecker::checkPointerEscape( ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind) const { … } ExplodedNode * FuchsiaHandleChecker::reportLeaks(ArrayRef<SymbolRef> LeakedHandles, CheckerContext &C, ExplodedNode *Pred) const { … } void FuchsiaHandleChecker::reportDoubleRelease(SymbolRef HandleSym, const SourceRange &Range, CheckerContext &C) const { … } void FuchsiaHandleChecker::reportUnownedRelease(SymbolRef HandleSym, const SourceRange &Range, CheckerContext &C) const { … } void FuchsiaHandleChecker::reportUseAfterFree(SymbolRef HandleSym, const SourceRange &Range, CheckerContext &C) const { … } void FuchsiaHandleChecker::reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, CheckerContext &C, const SourceRange *Range, const BugType &Type, StringRef Msg) const { … } void ento::registerFuchsiaHandleChecker(CheckerManager &mgr) { … } bool ento::shouldRegisterFuchsiaHandleChecker(const CheckerManager &mgr) { … } void FuchsiaHandleChecker::printState(raw_ostream &Out, ProgramStateRef State, const char *NL, const char *Sep) const { … }