mirror of
https://github.com/capstone-engine/llvm-capstone.git
synced 2024-12-12 09:41:26 +00:00
[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
This commit is contained in:
parent
07861e955d
commit
82923c71ef
@ -1335,6 +1335,31 @@ Warns if 'CFArray', 'CFDictionary', 'CFSet' are created with non-pointer-size va
|
||||
&kCFTypeArrayCallBacks); // warn
|
||||
}
|
||||
|
||||
Fuchsia
|
||||
^^^^^^^
|
||||
|
||||
Fuchsia is an open source capability-based operating system currently being
|
||||
developed by Google. This section describes checkers that can find various
|
||||
misuses of Fuchsia APIs.
|
||||
|
||||
.. _fuchsia-HandleChecker:
|
||||
|
||||
fuchsia.HandleChecker
|
||||
""""""""""""""""""""""""""""
|
||||
Handles identify resources. Similar to pointers they can be leaked,
|
||||
double freed, or use after freed. This check attempts to find such problems.
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
void checkLeak08(int tag) {
|
||||
zx_handle_t sa, sb;
|
||||
zx_channel_create(0, &sa, &sb);
|
||||
if (tag)
|
||||
zx_handle_close(sa);
|
||||
use(sb); // Warn: Potential leak of handle
|
||||
zx_handle_close(sb);
|
||||
}
|
||||
|
||||
|
||||
.. _alpha-checkers:
|
||||
|
||||
|
@ -108,6 +108,8 @@ def CloneDetectionAlpha : Package<"clone">, ParentPackage<Alpha>;
|
||||
|
||||
def NonDeterminismAlpha : Package<"nondeterminism">, ParentPackage<Alpha>;
|
||||
|
||||
def Fuchsia : Package<"fuchsia">;
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Core Checkers.
|
||||
//===----------------------------------------------------------------------===//
|
||||
@ -1423,3 +1425,16 @@ def PointerSortingChecker : Checker<"PointerSorting">,
|
||||
Documentation<HasDocumentation>;
|
||||
|
||||
} // end alpha.nondeterminism
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Fuchsia checkers.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
let ParentPackage = Fuchsia in {
|
||||
|
||||
def FuchsiaHandleChecker : Checker<"HandleChecker">,
|
||||
HelpText<"A Checker that detect leaks related to Fuchsia handles">,
|
||||
Documentation<HasDocumentation>;
|
||||
|
||||
} // end fuchsia
|
||||
|
||||
|
@ -213,6 +213,22 @@ public:
|
||||
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<BugReport> R) {
|
||||
Changed = true;
|
||||
|
@ -2794,6 +2794,8 @@ static void RenderAnalyzerOptions(const ArgList &Args, ArgStringList &CmdArgs,
|
||||
CmdArgs.push_back(
|
||||
"-analyzer-checker=security.insecureAPI.decodeValueOfObjCType");
|
||||
}
|
||||
else if (Triple.isOSFuchsia())
|
||||
CmdArgs.push_back("-analyzer-checker=fuchsia");
|
||||
|
||||
CmdArgs.push_back("-analyzer-checker=deadcode");
|
||||
|
||||
|
@ -38,6 +38,7 @@ add_clang_library(clangStaticAnalyzerCheckers
|
||||
EnumCastOutOfRangeChecker.cpp
|
||||
ExprInspectionChecker.cpp
|
||||
FixedAddressChecker.cpp
|
||||
FuchsiaHandleChecker.cpp
|
||||
GCDAntipatternChecker.cpp
|
||||
GenericTaintChecker.cpp
|
||||
GTestChecker.cpp
|
||||
|
486
clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp
Normal file
486
clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
285
clang/test/Analysis/fuchsia_handle.cpp
Normal file
285
clang/test/Analysis/fuchsia_handle.cpp
Normal file
@ -0,0 +1,285 @@
|
||||
// RUN: %clang_analyze_cc1 -analyzer-checker=core,fuchsia.HandleChecker -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)
|
||||
#define ZX_HANDLE_INVALID 0
|
||||
|
||||
#if defined(__clang__)
|
||||
#define ZX_HANDLE_ACQUIRE __attribute__((acquire_handle("Fuchsia")))
|
||||
#define ZX_HANDLE_RELEASE __attribute__((release_handle("Fuchsia")))
|
||||
#define ZX_HANDLE_USE __attribute__((use_handle("Fuchsia")))
|
||||
#else
|
||||
#define ZX_HANDLE_ACQUIRE
|
||||
#define ZX_HANDLE_RELEASE
|
||||
#define ZX_HANDLE_USE
|
||||
#endif
|
||||
|
||||
zx_status_t zx_channel_create(
|
||||
uint32_t options,
|
||||
zx_handle_t *out0 ZX_HANDLE_ACQUIRE,
|
||||
zx_handle_t *out1 ZX_HANDLE_ACQUIRE);
|
||||
|
||||
zx_status_t zx_handle_close(
|
||||
zx_handle_t handle ZX_HANDLE_RELEASE);
|
||||
|
||||
void escape1(zx_handle_t *in);
|
||||
void escape2(zx_handle_t in);
|
||||
void (*escape3)(zx_handle_t) = escape2;
|
||||
|
||||
void use1(const zx_handle_t *in ZX_HANDLE_USE);
|
||||
void use2(zx_handle_t in ZX_HANDLE_USE);
|
||||
|
||||
void moreArgs(zx_handle_t, int, ...);
|
||||
void lessArgs(zx_handle_t, int a = 5);
|
||||
|
||||
// To test if argument indexes are OK for operator calls.
|
||||
struct MyType {
|
||||
ZX_HANDLE_ACQUIRE
|
||||
zx_handle_t operator+(zx_handle_t ZX_HANDLE_RELEASE replace);
|
||||
};
|
||||
|
||||
void checkInvalidHandle01() {
|
||||
zx_handle_t sa, sb;
|
||||
zx_channel_create(0, &sa, &sb);
|
||||
if (sa == ZX_HANDLE_INVALID)
|
||||
;
|
||||
// Will we ever see a warning like below?
|
||||
// We eagerly replace the symbol with a constant and lose info...
|
||||
use2(sa); // TODOexpected-warning {{Use of an invalid handle}}
|
||||
zx_handle_close(sb);
|
||||
zx_handle_close(sa);
|
||||
}
|
||||
|
||||
void checkInvalidHandle2() {
|
||||
zx_handle_t sa, sb;
|
||||
zx_channel_create(0, &sa, &sb);
|
||||
if (sb != ZX_HANDLE_INVALID)
|
||||
zx_handle_close(sb);
|
||||
if (sa != ZX_HANDLE_INVALID)
|
||||
zx_handle_close(sa);
|
||||
}
|
||||
|
||||
void checkNoCrash01() {
|
||||
zx_handle_t sa, sb;
|
||||
zx_channel_create(0, &sa, &sb);
|
||||
moreArgs(sa, 1, 2, 3, 4, 5);
|
||||
lessArgs(sa);
|
||||
zx_handle_close(sa);
|
||||
zx_handle_close(sb);
|
||||
}
|
||||
|
||||
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 checkLeak01(int tag) {
|
||||
zx_handle_t sa, sb;
|
||||
if (zx_channel_create(0, &sa, &sb))
|
||||
return;
|
||||
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}}
|
||||
}
|
||||
|
||||
void checkMemberOperatorIndices() {
|
||||
zx_handle_t sa, sb, sc;
|
||||
zx_channel_create(0, &sa, &sb);
|
||||
zx_handle_close(sb);
|
||||
MyType t;
|
||||
sc = t + sa;
|
||||
zx_handle_close(sc);
|
||||
}
|
||||
|
||||
// RAII
|
||||
|
||||
template <typename T>
|
||||
struct HandleWrapper {
|
||||
~HandleWrapper() { close(); }
|
||||
void close() {
|
||||
if (handle != ZX_HANDLE_INVALID)
|
||||
zx_handle_close(handle);
|
||||
}
|
||||
T *get_handle_address() { return &handle; }
|
||||
private:
|
||||
T handle;
|
||||
};
|
||||
|
||||
void doNotWarnOnRAII() {
|
||||
HandleWrapper<zx_handle_t> w1;
|
||||
zx_handle_t sb;
|
||||
if (zx_channel_create(0, w1.get_handle_address(), &sb))
|
||||
return;
|
||||
zx_handle_close(sb);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct HandleWrapperUnkonwDtor {
|
||||
~HandleWrapperUnkonwDtor();
|
||||
void close() {
|
||||
if (handle != ZX_HANDLE_INVALID)
|
||||
zx_handle_close(handle);
|
||||
}
|
||||
T *get_handle_address() { return &handle; }
|
||||
private:
|
||||
T handle;
|
||||
};
|
||||
|
||||
void doNotWarnOnUnkownDtor() {
|
||||
HandleWrapperUnkonwDtor<zx_handle_t> w1;
|
||||
zx_handle_t sb;
|
||||
if (zx_channel_create(0, w1.get_handle_address(), &sb))
|
||||
return;
|
||||
zx_handle_close(sb);
|
||||
}
|
||||
|
||||
// Various escaping scenarios
|
||||
|
||||
zx_handle_t *get_handle_address();
|
||||
|
||||
void escape_store_to_escaped_region01() {
|
||||
zx_handle_t sb;
|
||||
if (zx_channel_create(0, get_handle_address(), &sb))
|
||||
return;
|
||||
zx_handle_close(sb);
|
||||
}
|
||||
|
||||
struct object {
|
||||
zx_handle_t *get_handle_address();
|
||||
};
|
||||
|
||||
void escape_store_to_escaped_region02(object &o) {
|
||||
zx_handle_t sb;
|
||||
// Same as above.
|
||||
if (zx_channel_create(0, o.get_handle_address(), &sb))
|
||||
return;
|
||||
zx_handle_close(sb);
|
||||
}
|
||||
|
||||
void escape_store_to_escaped_region03(object o) {
|
||||
zx_handle_t sb;
|
||||
// Should we consider the pointee of get_handle_address escaped?
|
||||
// Maybe we only should it consider escaped if o escapes?
|
||||
if (zx_channel_create(0, o.get_handle_address(), &sb))
|
||||
return;
|
||||
zx_handle_close(sb);
|
||||
}
|
||||
|
||||
void escape_through_call(int tag) {
|
||||
zx_handle_t sa, sb;
|
||||
if (zx_channel_create(0, &sa, &sb))
|
||||
return;
|
||||
escape1(&sa);
|
||||
if (tag)
|
||||
escape2(sb);
|
||||
else
|
||||
escape3(sb);
|
||||
}
|
||||
|
||||
struct have_handle {
|
||||
zx_handle_t h;
|
||||
zx_handle_t *hp;
|
||||
};
|
||||
|
||||
void escape_through_store01(have_handle *handle) {
|
||||
zx_handle_t sa;
|
||||
if (zx_channel_create(0, &sa, handle->hp))
|
||||
return;
|
||||
handle->h = sa;
|
||||
}
|
||||
|
||||
have_handle global;
|
||||
void escape_through_store02() {
|
||||
zx_handle_t sa;
|
||||
if (zx_channel_create(0, &sa, global.hp))
|
||||
return;
|
||||
global.h = sa;
|
||||
}
|
||||
|
||||
have_handle escape_through_store03() {
|
||||
zx_handle_t sa, sb;
|
||||
if (zx_channel_create(0, &sa, &sb))
|
||||
return {0, nullptr};
|
||||
zx_handle_close(sb);
|
||||
return {sa, nullptr};
|
||||
}
|
||||
|
||||
void escape_structs(have_handle *);
|
||||
void escape_transitively01() {
|
||||
zx_handle_t sa, sb;
|
||||
if (zx_channel_create(0, &sa, &sb))
|
||||
return;
|
||||
have_handle hs[2];
|
||||
hs[1] = {sa, &sb};
|
||||
escape_structs(hs);
|
||||
}
|
||||
|
||||
void escape_top_level_pointees(zx_handle_t *h) {
|
||||
zx_handle_t h2;
|
||||
if (zx_channel_create(0, h, &h2))
|
||||
return;
|
||||
zx_handle_close(h2);
|
||||
} // *h should be escaped here. Right?
|
Loading…
Reference in New Issue
Block a user