Index: include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/Checkers.td +++ include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -69,6 +69,7 @@ def OSX : Package<"osx">; def OSXAlpha : Package<"osx">, InPackage, Hidden; def OSXOptIn : Package<"osx">, InPackage; +def Magenta : Package<"magenta">, InPackage; def Cocoa : Package<"cocoa">, InPackage; def CocoaAlpha : Package<"cocoa">, InPackage, Hidden; @@ -770,3 +771,15 @@ DescFile<"UnixAPIChecker.cpp">; } // end optin.portability + +//===----------------------------------------------------------------------===// +// Magenta Specified Checkers +//===----------------------------------------------------------------------===// + +let ParentPackage = Magenta in { + +def MagentaHandleChecker : Checker<"MagentaHandleChecker">, + HelpText<"A Checker that detect leaks related to Magenta handles">, + DescFile<"MagentaHandleChecker.cpp">; + +} // end "magenta" Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -45,6 +45,7 @@ LocalizationChecker.cpp MacOSKeychainAPIChecker.cpp MacOSXAPIChecker.cpp + MagentaHandleChecker.cpp MallocChecker.cpp MallocOverflowSecurityChecker.cpp MallocSizeofChecker.cpp Index: lib/StaticAnalyzer/Checkers/MagentaHandleChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/MagentaHandleChecker.cpp @@ -0,0 +1,876 @@ +//== MagentaHandleChecker.cpp - Magenta Handle Checker------------*- C++-*--==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This checker checks if the handle of magenta kernel is properly used +// according to following rules. +// - If a handle is allocated, it should be closed/released before execution +// ends. +// - If a handle is closed/released, it should not be closed/released again. +// - If a handle is closed/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. As illustrated in the following ASCII Art: +// +// mx_channel_write failed +// +---------+ +// | | +// | | +// | | As argument +// mx_channel_create succeeded +-+---------v-+ in uninlined +---------+ +// mx_channel_read succeeded | | calls | | +// +-----------------> Allocated +-------------------> Escaped | +// | | | As return | | +// | +-----+------++ value or +---------+ +// | | | assigned +// | mx_handle_close | | back to argument. +// | mx_channel_write| +--+ +// | succeeded | |handle out+--------+ +// | +----v-----+ |of scope | | +// | | | +----------> Leaked | +// +----------+--+ | Released | |(REPORT)| +// | | | | +--------+ +// | Not tracked <--+ +----+---+-+ +// | | | | | As argument +// +------+------+ | mx_handle_close| +------+in function +// | | | |call +// | | +----v-----+ | +-----------+ +// +---------+ | | | | | +// mx_channel_create failed | Double | +-----> Use after | +// mx_channel_read failed | released | | released | +// | (REPORT) | | (REPORT) | +// +----------+ +-----------+ +// +// +// If a tracked handle ends up in "Released" or "Escaped" state, we assume it +// is properly used. Otherwise a bug will be reported. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/AST/Attr.h" +#include "clang/AST/ParentMap.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TargetInfo.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 "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include +#include +#include +#include +#include + +using namespace clang; +using namespace ento; + +namespace { + +enum CallKind { + // Will add more calls in the future. For now only worried about the ones + // related to channel creation. + MX_CHANNEL_CREATE, + MX_CHANNEL_READ, + MX_CHANNEL_WRITE, + MX_HANDLE_CLOSE, + UNRELATED_CALL +}; + +struct HandleState { +private: + enum Kind { + // Handle is allocated + Allocated, + // Handle is released + Released, + // Handle is no longer trackable + Escaped + } K; + + HandleState(Kind k) : K(k) {} + +public: + bool operator==(const HandleState &X) const { return K == X.K; } + bool isAllocated() const { return K == Allocated; } + bool isReleased() const { return K == Released; } + bool isEscaped() const { return K == Escaped; } + + static HandleState getAllocated() { return HandleState(Allocated); } + + static HandleState getReleased() { return HandleState(Released); } + + static HandleState getEscaped() { return HandleState(Escaped); } + + void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } + + void dump(raw_ostream &OS) const { + switch (static_cast(K)) { +#define CASE(ID) case ID: OS << #ID; break; + CASE(Allocated) + CASE(Released) + CASE(Escaped) + } + } + LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); } +}; + +class MagentaHandleChecker + : public Checker, check::PointerEscape, + eval::Call, check::PostCall> { + std::unique_ptr LeakBugType; + std::unique_ptr DoubleReleaseBugType; + std::unique_ptr UseAfterFreeBugType; + mutable llvm::StringMap FunctionNameMap; + +public: + MagentaHandleChecker(); + // Checker callbacks + void checkPreCall(const CallEvent &Call, CheckerContext &Ctx) const; + + void checkPostCall(const CallEvent &Call, CheckerContext &Ctx) const; + + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &Ctx) const; + + void checkPreStmt(const ReturnStmt *RS, CheckerContext &Ctx) const; + + bool evalCall(const CallExpr *CE, CheckerContext &Ctx) const; + + ProgramStateRef checkPointerEscape(ProgramStateRef State, + const InvalidatedSymbols &Escaped, + const CallEvent *Call, + PointerEscapeKind Kind) const; + +private: + bool processMxChannelRead(const CallExpr *CE, CheckerContext &C) const; + + bool processMxChannelCreate(const CallExpr *CE, CheckerContext &C) const; + + bool processMxChannelWrite(const CallExpr *CE, CheckerContext &C) const; + + void processMxHandleClose(ProgramStateRef State, const CallEvent &Call, + CheckerContext &Ctx) const; + + void processUninlinedCalls(ProgramStateRef State, const CallEvent &Call, + CheckerContext &Ctx) const; + + // Bug report functions + void reportLeaks(ArrayRef LeakedHandles, CheckerContext &Ctx, + ExplodedNode *ErrorNode) const; + + void reportDoubleRelease(SymbolRef HandleSym, const SourceRange &Range, + CheckerContext &Ctx) const; + + void reportUseAfterFree(SymbolRef HandleSym, const SourceRange &Range, + CheckerContext &Ctx) const; + // Utility functions + CallKind CheckCallSignature(const CallEvent &Call) const; + + CallKind CheckCallSignature(const FunctionDecl *FuncDecl) const; + + bool CheckExprIsZero(const Expr *ArgExpr, CheckerContext &Ctx) const; + + // Return true only if it is certain that Sym will be Zero + static bool CheckSymbolConstraintToZero(SymbolRef Sym, ProgramStateRef State); + + // Return true only if it is certain that Sym will not be Zero + static bool CheckSymbolConstraintToNotZero(SymbolRef Sym, + ProgramStateRef State); + + bool isLeaked(SymbolRef SymHandle, const HandleState &CurHandleState, + bool isSymDead, ProgramStateRef State) const; +}; +} // end anonymous namespace + +// Handle -> HandleState map +REGISTER_MAP_WITH_PROGRAMSTATE(HStateMap, SymbolRef, HandleState) + +MagentaHandleChecker::MagentaHandleChecker() { + // Initialize the bug types. + LeakBugType.reset( + new BugType(this, "MX handle leak", "Magenta Handle Error")); + DoubleReleaseBugType.reset( + new BugType(this, "MX handle double release", "Magenta Handle Error")); + UseAfterFreeBugType.reset( + new BugType(this, "MX handle use after free", "Magenta Handle Error")); + + FunctionNameMap = {{"mx_channel_create", MX_CHANNEL_CREATE}, + {"mx_channel_read", MX_CHANNEL_READ}, + {"mx_handle_close", MX_HANDLE_CLOSE}, + {"mx_channel_write", MX_CHANNEL_WRITE}}; +} + +CallKind +MagentaHandleChecker::CheckCallSignature(const FunctionDecl *FuncDecl) const { + if (!FuncDecl) + return UNRELATED_CALL; + + DeclarationName DeclName = FuncDecl->getDeclName(); + if (!DeclName.isIdentifier()) + return UNRELATED_CALL; + + // Use function name to match target function calls + // Type signature check is not necessary because C function + // cannot be overridden + StringRef FuncName = FuncDecl->getName(); + if (FunctionNameMap.count(FuncName) != 1) + return UNRELATED_CALL; + return FunctionNameMap[FuncName]; +} + +CallKind MagentaHandleChecker::CheckCallSignature(const CallEvent &Call) const { + if (const AnyFunctionCall *FuncCall = dyn_cast(&Call)) { + const FunctionDecl *FuncDecl = FuncCall->getDecl(); + if (!FuncDecl) + return UNRELATED_CALL; + + return CheckCallSignature(FuncDecl); + } + return UNRELATED_CALL; +} + +bool MagentaHandleChecker::isLeaked(SymbolRef SymHandle, + const HandleState &CurHandleState, + bool isSymDead, + ProgramStateRef State) const { + if (isSymDead && CurHandleState.isAllocated()) { + // Check if handle value is MX_HANDLE_INVALID. If so, not a leak. + if (CheckSymbolConstraintToZero(SymHandle, State)) + return false; + + // Handle is allocated and dead. Leaked + return true; + } + // Handle not dead. Not a leak. + return false; +} + +bool MagentaHandleChecker::CheckSymbolConstraintToZero(SymbolRef Sym, + ProgramStateRef State) { + ConstraintManager &CMgr = State->getConstraintManager(); + ConditionTruthVal IsZero = CMgr.isNull(State, Sym); + if (IsZero.isConstrained()) { + // The value can be compared to zero + if (IsZero.isConstrainedTrue()) + return true; + } + return false; +} + +bool MagentaHandleChecker::CheckSymbolConstraintToNotZero( + SymbolRef Sym, ProgramStateRef State) { + ConstraintManager &CMgr = State->getConstraintManager(); + ConditionTruthVal IsZero = CMgr.isNull(State, Sym); + if (IsZero.isConstrained()) { + // The value can be compared to zero + if (IsZero.isConstrainedFalse()) + return true; + } + return false; +} + +ProgramStateRef MagentaHandleChecker::checkPointerEscape( + ProgramStateRef State, const InvalidatedSymbols &Escaped, + const CallEvent *Call, PointerEscapeKind Kind) const { + HStateMapTy TrackedHandle = State->get(); + SmallVector EscapedSymbolRef; + for (auto &CurItem : TrackedHandle) { + SymbolRef Sym = CurItem.first; + if (Escaped.count(Sym) == 1) + EscapedSymbolRef.push_back(Sym); + + const SymbolDerived *ElementSym = dyn_cast(Sym); + if (!ElementSym) + continue; + + const SymbolRef ParentSymbol = ElementSym->getParentSymbol(); + if (Escaped.count(ParentSymbol) == 1) + EscapedSymbolRef.push_back(Sym); + + } + + for (SymbolRef EscapedHandle : EscapedSymbolRef) { + State = State->remove(EscapedHandle); + State = State->set(EscapedHandle, HandleState::getEscaped()); + } + + // Currently only handles passed through pointer/arrays are considered in + // this callback. There are some cases that the values of handles are wrapped + // in multiple layers of struct. Current implementation may failed to treat + // these handles as escaped, which causes false positives. + // TODO: Improve handle escape detection in the future. + + return State; +} + +bool MagentaHandleChecker::CheckExprIsZero(const Expr *ArgExpr, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + const LocationContext *LCtx = Ctx.getLocationContext(); + SVal ExprSVal = State->getSVal(ArgExpr, LCtx); + int64_t ExtVal; + if (ExprSVal.getBaseKind() == SVal::NonLocKind && + ExprSVal.getSubKind() == nonloc::ConcreteIntKind) { + ExtVal = ExprSVal.castAs().getValue().getExtValue(); + } else if (ExprSVal.getBaseKind() == SVal::LocKind && + ExprSVal.getSubKind() == loc::ConcreteIntKind) { + ExtVal = ExprSVal.castAs().getValue().getExtValue(); + } else { + return false; + } + if (ExtVal == 0) + return true; + + return false; +} + +void MagentaHandleChecker::checkPreCall(const CallEvent &Call, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + + switch (CheckCallSignature(Call)) { + case MX_HANDLE_CLOSE: + processMxHandleClose(State, Call, Ctx); + break; + default: + break; + } +} + +void MagentaHandleChecker::checkPostCall(const CallEvent &Call, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + + switch (CheckCallSignature(Call)) { + case UNRELATED_CALL: + processUninlinedCalls(State, Call, Ctx); + break; + default: + break; + } +} + +void MagentaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + SmallVector LeakVector; + HStateMapTy TrackedHandle = State->get(); + for (auto &CurItem : TrackedHandle) { + SymbolRef Sym = CurItem.first; + // Workaround for zombie symbol issue. + bool IsSymDead = SymReaper.maybeDead(Sym); + const HandleState &CurHandleState = CurItem.second; + if (isLeaked(Sym, CurHandleState, IsSymDead, State)) + LeakVector.push_back(Sym); + + if (IsSymDead) + State = State->remove(Sym); + } + + if (!LeakVector.empty()) { + ExplodedNode *N = Ctx.generateNonFatalErrorNode(State); + if (!N) + return; + + reportLeaks(LeakVector, Ctx, N); + } else { + Ctx.addTransition(State); + } +} + +void MagentaHandleChecker::checkPreStmt(const ReturnStmt *RS, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + const auto *LCtx = Ctx.getLocationContext(); + if (!LCtx) + return; + + const auto *SFCtx = LCtx->getCurrentStackFrame(); + if (!SFCtx) + return; + + const Expr *RetE = RS->getRetValue(); + if (!RetE) + return; + + SVal SValRet = State->getSVal(RetE, LCtx); + SymbolRef SymRet = SValRet.getAsSymbol(); + if (!SymRet) + return; + + const HandleState *SymState = State->get(SymRet); + // Check if value returned in this ReturnStmt is tracked by analyzer + if (!SymState) + return; + + if (SymState->isReleased()) { + // Use after release bug. + ExplodedNode *N = Ctx.generateErrorNode(State); + if (!N) + return; + + reportUseAfterFree(SymRet, RS->getSourceRange(), Ctx); + return; + } + State = State->set(SymRet, HandleState::getEscaped()); + Ctx.addTransition(State); +} + +bool MagentaHandleChecker::evalCall(const CallExpr *CE, + CheckerContext &C) const { + const FunctionDecl *FuncDecl = C.getCalleeDecl(CE); + switch (CheckCallSignature(FuncDecl)) { + case MX_CHANNEL_READ: + return processMxChannelRead(CE, C); + case MX_CHANNEL_CREATE: + return processMxChannelCreate(CE, C); + case MX_CHANNEL_WRITE: + return processMxChannelWrite(CE, C); + default: + break; + } + return false; +} + +void MagentaHandleChecker::processUninlinedCalls(ProgramStateRef State, + const CallEvent &Call, + CheckerContext &Ctx) const { + // Process the handle escaped situation. + // When a function is not inlined for any reasons, if one of its + // arguments is an acquired handle, treat it as an escaped handle. + // This is just a naive approach to reduce the false positive rate, should + // be refined later, e.g. through annotation + // TODO: Use annotation to make it more accurate. + const FunctionDecl *FD = dyn_cast_or_null(Call.getDecl()); + if (!FD) + return; + + if (Ctx.wasInlined) + return; + + unsigned ArgsCount = Call.getNumArgs(); + bool StateChanged = false; + if (!ArgsCount) + // No argument passed to this call, ignore it + return; + for (unsigned i = 0; i < ArgsCount; i++) { + SVal ArgSVal = Call.getArgSVal(i); + SymbolRef ArgSym = ArgSVal.getAsSymbol(); + // Check if the argument is a pointer. If so, it should have been handled + // by checkPointerEscape, can be ignored here. + if (!ArgSym) + continue; + + const HandleState *CurHandleState = State->get(ArgSym); + // Check if the handle is tracked by analyzer. If not, we ignore it here. + if (!CurHandleState) + continue; + + if (CurHandleState->isReleased()) + // Use after free + reportUseAfterFree(ArgSym, Call.getSourceRange(), Ctx); + State = State->set(ArgSym, HandleState::getEscaped()); + StateChanged = true; + } + if (StateChanged) + Ctx.addTransition(State); +} + +bool MagentaHandleChecker::processMxChannelRead(const CallExpr *CE, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + const LocationContext *LCtx = Ctx.getLocationContext(); + ProgramStateManager &Mgr = State->getStateManager(); + ASTContext &ASTCtx = Mgr.getContext(); + SValBuilder &svalBuilder = Ctx.getSValBuilder(); + MemRegionManager &memRegionMgr = svalBuilder.getRegionManager(); + + // If handle related args are null. Do not process this call + const Expr *HandleBufExpr = CE->getArg(3); + const Expr *InputSizeExpr = CE->getArg(5); + const Expr *OutputSizeExpr = CE->getArg(7); + if (CheckExprIsZero(HandleBufExpr, Ctx) || + CheckExprIsZero(InputSizeExpr, Ctx) || + CheckExprIsZero(OutputSizeExpr, Ctx)) + return false; + + // Conjure a failed state which does not allocate any handle + DefinedOrUnknownSVal FailedStatusSVal = + svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, Ctx.blockCount()); + ProgramStateRef FailedState = State->BindExpr(CE, LCtx, FailedStatusSVal); + FailedState = FailedState->assumeInclusiveRange( + FailedStatusSVal, llvm::APSInt::get(INT_MIN), llvm::APSInt::get(-1), + true); + + // Conjure a successful state with return value is 0 + // Invalidate the handlebuf pointer and handle_count pointer + SmallVector InvalidateSVal; + InvalidateSVal.push_back(State->getSVal(HandleBufExpr, LCtx)); + InvalidateSVal.push_back(State->getSVal(OutputSizeExpr, LCtx)); + State = State->invalidateRegions(InvalidateSVal, CE, Ctx.blockCount(), LCtx, + false, nullptr, nullptr, nullptr); + + SVal StatusSVal = Ctx.getSValBuilder().makeIntVal(llvm::APSInt::get(0)); + State = State->BindExpr(CE, LCtx, StatusSVal); + + const MemRegion *MemHandleBuf = + State->getSVal(HandleBufExpr, LCtx).getAsRegion(); + if (!MemHandleBuf) + return false; + + // The handle buffer is not an array or a pointer to a memory space + // It might be a pointer to a local variable. Currently ignore it + // TODO: Handle the case when the handle buffer is a single mx_handle_t + // variable + if (!isa(MemHandleBuf)) + return false; + + const SubRegion *SuperRegionSub = dyn_cast( + dyn_cast(MemHandleBuf)->getSuperRegion()); + if (!SuperRegionSub) + return false; + + SVal HandleBufSVal = State->getSVal(MemHandleBuf); + SymbolRef SymHandleBuf = HandleBufSVal.getAsSymbol(); + if (!SymHandleBuf) + return false; + + SymbolRef ParentSymbol = + dyn_cast(SymHandleBuf)->getParentSymbol(); + if (!ParentSymbol) + return false; + + // The handle buffer is a typedValueRegion + const TypedValueRegion *TR = cast(MemHandleBuf); + QualType HandleType = TR->getValueType(); + + // Construct derived symbol associated with handle buffer + // 1. When the supplied handle count can be determined at call time + // (e.g. const/known variable), conjure exactly the number of handle symbols + // 2. if 1. is not satisfied, conjure exactly 4 handles + SVal InputSizeSVal = State->getSVal(InputSizeExpr, LCtx); + int64_t ExtVal = 4; + if (InputSizeSVal.getBaseKind() == SVal::NonLocKind && + InputSizeSVal.getSubKind() == nonloc::ConcreteIntKind) { + ExtVal = + InputSizeSVal.castAs().getValue().getExtValue(); + } else if (InputSizeSVal.getBaseKind() == SVal::LocKind && + InputSizeSVal.getSubKind() == loc::ConcreteIntKind) { + ExtVal = InputSizeSVal.castAs().getValue().getExtValue(); + } + + if (ExtVal < 0) + return false; + + for (int i = 0; i < ExtVal; ++i) { + NonLoc AryIndex = svalBuilder.makeArrayIndex(i); + const ElementRegion *CurElementRegion = memRegionMgr.getElementRegion( + HandleType, AryIndex, SuperRegionSub, ASTCtx); + if (!CurElementRegion) + return false; + + // Build derived SVal for elements in the handle buffer + DefinedOrUnknownSVal CurSVal = svalBuilder.getDerivedRegionValueSymbolVal( + ParentSymbol, CurElementRegion); + SymbolRef CurSymbol = CurSVal.getAsSymbol(); + if (!CurSymbol) + return false; + // A valid handle should be larger than 0. Add this constraint to the handle + if (CurSVal.getBaseKind() == SVal::NonLocKind) + State->assumeInclusiveRange(CurSVal, llvm::APSInt::get(1), + llvm::APSInt::get(2147483647), true); + + // Set state for each constructed symbol + State = State->set(CurSymbol, HandleState::getAllocated()); + } + // Process the actual_handle returned + SVal ActHandleSVal = State->getSVal(OutputSizeExpr, LCtx); + SVal ConcreteInvSVal = svalBuilder.makeIntVal(llvm::APSInt::get(ExtVal)); + State = State->bindLoc(ActHandleSVal, ConcreteInvSVal, LCtx); + + Ctx.addTransition(State); + Ctx.addTransition(FailedState); + + return true; +} + +bool MagentaHandleChecker::processMxChannelCreate(const CallExpr *CE, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + const LocationContext *LCtx = Ctx.getLocationContext(); + const Expr *Handle1Expr = CE->getArg(1); + const Expr *Handle2Expr = CE->getArg(2); + if (!(Handle1Expr && Handle2Expr)) + return false; + + // Conjure a failed state with no handle allocated and status less than 0 + DefinedOrUnknownSVal FailedSVal = Ctx.getSValBuilder().conjureSymbolVal( + nullptr, CE, LCtx, Ctx.blockCount()); + ProgramStateRef FailedState = State->BindExpr(CE, LCtx, FailedSVal); + FailedState = FailedState->assumeInclusiveRange( + FailedSVal, llvm::APSInt::get(INT_MIN), llvm::APSInt::get(-1), true); + + SmallVector invalidateSVal; + + // Conjure a successful state with status = 0 + invalidateSVal.push_back(State->getSVal(Handle1Expr, LCtx)); + invalidateSVal.push_back(State->getSVal(Handle2Expr, LCtx)); + + State = State->invalidateRegions(invalidateSVal, CE, Ctx.blockCount(), LCtx, + false, nullptr, nullptr, nullptr); + SVal StatusSVal = Ctx.getSValBuilder().makeIntVal(llvm::APSInt::get(0)); + State = State->BindExpr(CE, LCtx, StatusSVal); + + const MemRegion *MemHandle1 = State->getSVal(Handle1Expr, LCtx).getAsRegion(); + const MemRegion *MemHandle2 = State->getSVal(Handle2Expr, LCtx).getAsRegion(); + if (!MemHandle1 || !MemHandle2) + return false; + + SVal SValHandle1 = State->getSVal(MemHandle1); + SVal SValHandle2 = State->getSVal(MemHandle2); + SymbolRef SymHandle1 = SValHandle1.getAsSymbol(); + SymbolRef SymHandle2 = SValHandle2.getAsSymbol(); + if (!SymHandle1 || !SymHandle2) + return false; + // Valid handle should be larger than 0 + DefinedOrUnknownSVal DSValHandle1 = + SValHandle1.castAs(); + DefinedOrUnknownSVal DSValHandle2 = + SValHandle2.castAs(); + if (DSValHandle1.getBaseKind() == SVal::NonLocKind && + DSValHandle2.getBaseKind() == SVal::NonLocKind) { + State->assumeInclusiveRange(DSValHandle1, llvm::APSInt::get(1), + llvm::APSInt::get(2147483647), true); + State->assumeInclusiveRange(DSValHandle2, llvm::APSInt::get(1), + llvm::APSInt::get(2147483647), true); + } + State = State->set(SymHandle1, HandleState::getAllocated()); + State = State->set(SymHandle2, HandleState::getAllocated()); + Ctx.addTransition(State); + Ctx.addTransition(FailedState); + return true; +} + +bool MagentaHandleChecker::processMxChannelWrite(const CallExpr *CE, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + const LocationContext *LCtx = Ctx.getLocationContext(); + ProgramStateManager &Mgr = State->getStateManager(); + ASTContext &ASTCtx = Mgr.getContext(); + SValBuilder &svalBuilder = Ctx.getSValBuilder(); + MemRegionManager &memRegionMgr = svalBuilder.getRegionManager(); + + // If handle related arguments are 0, do not process this call + const Expr *HandleBufExpr = CE->getArg(4); + const Expr *InputSizeExpr = CE->getArg(5); + if (CheckExprIsZero(HandleBufExpr, Ctx) || + CheckExprIsZero(InputSizeExpr, Ctx)) + return false; + + // Conjure a failed state which does not release any handle + DefinedOrUnknownSVal FailedStatusSVal = + svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, Ctx.blockCount()); + ProgramStateRef FailedState = State->BindExpr(CE, LCtx, FailedStatusSVal); + FailedState = FailedState->assumeInclusiveRange( + FailedStatusSVal, llvm::APSInt::get(INT_MIN), llvm::APSInt::get(-1), + true); + + // Conjure a successful state + SVal StatusSVal = Ctx.getSValBuilder().makeIntVal(llvm::APSInt::get(0)); + State = State->BindExpr(CE, LCtx, StatusSVal); + + // Determine the number of handles + SVal InputSizeSVal = State->getSVal(InputSizeExpr, LCtx); + int64_t ExtVal = -1; + if (InputSizeSVal.getBaseKind() == SVal::NonLocKind && + InputSizeSVal.getSubKind() == nonloc::ConcreteIntKind) { + ExtVal = + InputSizeSVal.castAs().getValue().getExtValue(); + } else if (InputSizeSVal.getBaseKind() == SVal::LocKind && + InputSizeSVal.getSubKind() == loc::ConcreteIntKind) { + ExtVal = InputSizeSVal.castAs().getValue().getExtValue(); + } + // Retrive MemRegion of the handle buffer + SVal HandleBufSValOrig = State->getSVal(HandleBufExpr, LCtx); + const MemRegion *MemHandleBuf = HandleBufSValOrig.getAsRegion(); + + if (isa(MemHandleBuf)) { + // Handle buffer is a pointer to a local mx_handle_t variable + // Only 1 handle will be transmitted + ExtVal = 1; + SVal HandleBufSVal = State->getSVal(MemHandleBuf); + SymbolRef SymHandleBuf = HandleBufSVal.getAsSymbol(); + if (!SymHandleBuf) + return false; + + State = State->set(SymHandleBuf, HandleState::getReleased()); + Ctx.addTransition(FailedState); + Ctx.addTransition(State); + return true; + } + // Handle buffer is a pointer to a large region + const SubRegion *SuperRegionSub = + isa(MemHandleBuf) + ? dyn_cast( + dyn_cast(MemHandleBuf)->getSuperRegion()) + : NULL; + if (!SuperRegionSub) + return false; + + SVal HandleBufSVal = State->getSVal(MemHandleBuf); + SymbolRef SymHandleBuf = HandleBufSVal.getAsSymbol(); + + if (!SymHandleBuf) + // The handle buffer is not initialized or the handles are not allocated. + // Rare to see in practice. + // TODO: Rethink this situation in the future. + return false; + if (!isa(SymHandleBuf)) + return false; + + SymbolRef ParentSymbol = + dyn_cast(SymHandleBuf)->getParentSymbol(); + if (!ParentSymbol) + return false; + + const TypedValueRegion *TR = cast(MemHandleBuf); + QualType HandleType = TR->getValueType(); + + bool TransitionHappened = false; + if (ExtVal < 0 || ExtVal > 5) { + // We don't know the exact number of handles that should be transferred or + // it is too large. Query the map and release all handles that may related. + HStateMapTy TrackedHandle = State->get(); + for (auto &CurItem : TrackedHandle) { + SymbolRef Sym = CurItem.first; + HandleState CurHandleState = CurItem.second; + const SymbolDerived *ElementSym = dyn_cast(Sym); + if (!ElementSym) + continue; + + if (ElementSym->getParentSymbol() == ParentSymbol) { + if (CurHandleState.isReleased()) { + // Double release + reportDoubleRelease(Sym, CE->getSourceRange(), Ctx); + } else { + State = State->set(Sym, HandleState::getReleased()); + TransitionHappened = true; + } + } + } + } else { + // We know the exact number of handles. Release exact number of handles + for (int i = 0; i < ExtVal; ++i) { + NonLoc AryIndex = svalBuilder.makeArrayIndex(i); + const ElementRegion *CurElementRegion = memRegionMgr.getElementRegion( + HandleType, AryIndex, SuperRegionSub, ASTCtx); + if (!CurElementRegion) + return false; + + // Build derived SVal for elements in the handle buffer + DefinedOrUnknownSVal CurSVal = svalBuilder.getDerivedRegionValueSymbolVal( + ParentSymbol, CurElementRegion); + SymbolRef CurSymbol = CurSVal.getAsSymbol(); + if (!CurSymbol) + return false; + + const HandleState *SymState = State->get(CurSymbol); + if (!SymState) + continue; + + if (SymState->isReleased()) { + reportDoubleRelease(CurSymbol, CE->getSourceRange(), Ctx); + } else { + State = State->set(CurSymbol, HandleState::getReleased()); + TransitionHappened = true; + } + } + } + if (TransitionHappened) + Ctx.addTransition(State); + + Ctx.addTransition(FailedState); + return true; +} + +void MagentaHandleChecker::processMxHandleClose(ProgramStateRef State, + const CallEvent &Call, + CheckerContext &Ctx) const { + // Retrive symbolic value for passed in handle + SymbolRef SymHandle = Call.getArgSVal(0).getAsSymbol(); + if (!SymHandle) + return; + + const HandleState *SymState = State->get(SymHandle); + if (!SymState) + return; + + if (SymState->isReleased()) { + // Double release + reportDoubleRelease(SymHandle, Call.getSourceRange(), Ctx); + return; + } + State = State->set(SymHandle, HandleState::getReleased()); + Ctx.addTransition(State); +} + +void MagentaHandleChecker::reportLeaks(ArrayRef LeakedHandles, + CheckerContext &C, + ExplodedNode *ErrorNode) const { + for (SymbolRef LeakedHandle : LeakedHandles) { + auto R = llvm::make_unique( + *LeakBugType, + "Allocated handle is never released; potential resource leak", + ErrorNode); + R->markInteresting(LeakedHandle); + R->disablePathPruning(); + C.emitReport(std::move(R)); + } +} + +void MagentaHandleChecker::reportDoubleRelease(SymbolRef HandleSym, + const SourceRange &Range, + CheckerContext &C) const { + ExplodedNode *ErrNode = C.generateErrorNode(); + if (!ErrNode) + return; + + auto R = llvm::make_unique( + *DoubleReleaseBugType, "Releasing a previously released handle", ErrNode); + R->addRange(Range); + R->markInteresting(HandleSym); + C.emitReport(std::move(R)); +} + +void MagentaHandleChecker::reportUseAfterFree(SymbolRef HandleSym, + const SourceRange &Range, + CheckerContext &Ctx) const { + ExplodedNode *ErrNode = Ctx.generateErrorNode(); + if (!ErrNode) + return; + + auto R = llvm::make_unique( + *UseAfterFreeBugType, "Using a previously released handle", ErrNode); + R->addRange(Range); + R->markInteresting(HandleSym); + Ctx.emitReport(std::move(R)); +} + +void ento::registerMagentaHandleChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} Index: test/Analysis/mxhandle.c =================================================================== --- /dev/null +++ test/Analysis/mxhandle.c @@ -0,0 +1,217 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.magenta.MagentaHandleChecker -analyzer-store=region -verify %s + +typedef __typeof__(sizeof(int)) size_t; +typedef int mx_status_t; +typedef __typeof__(sizeof(int)) mx_handle_t; +typedef unsigned int uint32_t; +#define NULL ((void *)0) + +mx_status_t mx_channel_create( + uint32_t options, + mx_handle_t* out0, + mx_handle_t* out1); + +mx_status_t mx_handle_close(mx_handle_t handle); + +mx_status_t mx_channel_read(mx_handle_t handle, uint32_t options, + void* bytes, mx_handle_t* handles, + uint32_t num_bytes, uint32_t num_handles, + uint32_t* actual_bytes, uint32_t* actual_handles); + +mx_status_t mx_channel_write(mx_handle_t handle, uint32_t options, + void* bytes, uint32_t num_bytes, + mx_handle_t* handles, uint32_t num_handles); + +void escapeMethod(mx_handle_t *in); +void useHandle(mx_handle_t handle); + +// End of declaration + +void checkNoLeak01() { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + mx_handle_close(sa); + mx_handle_close(sb); +} + +void checkNoLeak02() { + mx_handle_t ay[2]; + mx_channel_create(0, &ay[0], &ay[1]); + mx_handle_close(ay[0]); + mx_handle_close(ay[1]); +} + +void checkNoLeak03() { + mx_handle_t ay[2]; + mx_channel_create(0, &ay[0], &ay[1]); + for (int i = 0; i < 2; i++) { + mx_handle_close(ay[i]); + } +} + +mx_handle_t checkNoLeak04() { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + mx_handle_close(sa); + return sb; // no warning +} + +mx_handle_t checkNoLeak05(mx_handle_t *out1) { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + *out1 = sa; + return sb; // no warning +} + +void checkNoLeak06(mx_handle_t handle) { + mx_handle_t handlebuf[4]; + uint32_t hcount; + mx_channel_read(handle, 0, NULL, handlebuf, 0, 4, 0, &hcount); + for (int i = 0; i < hcount; i++) { + mx_handle_close(handlebuf[i]); + } +} + +void checkNoLeak07(mx_handle_t handle, uint32_t hcount) { + mx_handle_t handlebuf[6]; + mx_channel_read(handle, 0, NULL, handlebuf, 0, hcount, 0, &hcount); + for (int i = 0; i < hcount; i++) { + mx_handle_close(handlebuf[i]); + } +} + +void checkNoLeak08(mx_handle_t handle) { + mx_handle_t handlebuf[4]; + uint32_t hcount; + mx_channel_read(handle, 0, NULL, handlebuf, 0, 4, 0, &hcount); + if (mx_channel_write(handle, 0, NULL, 0, handlebuf, hcount) < 0) { + for (int i = 0; i < hcount; i++) { + mx_handle_close(handlebuf[i]); + } + } +} + +void checkNoLeak09(mx_handle_t handle, uint32_t hcount) { + mx_handle_t handlebuf[6]; + mx_channel_read(handle, 0, NULL, handlebuf, 0, hcount, 0, &hcount); + if (mx_channel_write(handle, 0, NULL, 0, handlebuf, hcount) < 0) { + for (int i = 0; i < hcount; i++) { + mx_handle_close(handlebuf[i]); + } + } +} + +void checkNoLeak10() { + mx_handle_t sa, sb; + if (mx_channel_create(0, &sa, &sb) < 0) { + return; + } + mx_handle_close(sa); + mx_handle_close(sb); +} + +void checkNoLeak11(mx_handle_t handle, uint32_t hcount) { + mx_handle_t handlebuf[6]; + mx_status_t r = mx_channel_read(handle, 0, NULL, + handlebuf, 0, hcount, 0, &hcount); + if (r < 0) { + return; + } + for (int i = 0; i < hcount; i++) { + mx_handle_close(handlebuf[i]); + } +} + +void checkNoLeak12(int tag) { + mx_handle_t sa, sb; + if (mx_channel_create(0, &sa, &sb) < 0) { + return; + } + if (tag) { + escapeMethod(&sa); + escapeMethod(&sb); + } + mx_handle_close(sa); + mx_handle_close(sb); +} + +void checkLeak01() { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); +} // expected-warning {{Allocated handle is never released; potential resource leak}} + +void checkLeak02(int tag) { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + if (tag) { + mx_handle_close(sa); + } + mx_handle_close(sb); // expected-warning {{Allocated handle is never released; potential resource leak}} +} + +void checkLeak03(mx_handle_t handle) { + mx_handle_t handlebuf[4]; + uint32_t hcount; + mx_status_t r = mx_channel_read(handle, 0, NULL, handlebuf, 0, 4, 0, &hcount); + if (r < 0) { + return; + } + for (int i = 0; i < hcount -1; i++) { + mx_handle_close(handlebuf[i]); + } +} // expected-warning {{Allocated handle is never released; potential resource leak}} + +void checkLeak04(mx_handle_t handle) { + mx_handle_t handlebuf[3]; + uint32_t hcount; + mx_status_t r = mx_channel_read(handle, 0, NULL, handlebuf, 0, 3, 0, &hcount); + if (r < 0) { + return; + } + if (mx_channel_write(handle, 0, NULL, 0, handlebuf, hcount - 1) < 0) { + for (int i = 0; i < hcount; i++) { + mx_handle_close(handlebuf[i]); + } + } +} // expected-warning {{Allocated handle is never released; potential resource leak}} + +void checkLeak05(mx_handle_t handle, uint32_t hcount) { + mx_handle_t handlebuf[6]; + mx_status_t r = mx_channel_read( + handle, 0, NULL, handlebuf, 0, hcount, 0, &hcount); + if (r < 0) { + return; + } + if (mx_channel_write(handle, 0, NULL, 0, handlebuf, hcount - 1) < 0) { + for (int i = 0; i < hcount; i++) { + mx_handle_close(handlebuf[i]); + } + } +} // expected-warning {{Allocated handle is never released; potential resource leak}} + +void checkLeak06(mx_handle_t handle, uint32_t hcount) { + mx_handle_t handlebuf[6]; + mx_channel_read(handle, 0, NULL, handlebuf, 0, hcount, 0, &hcount); + mx_channel_write(handle, 0, NULL, 0, handlebuf, hcount); // It may fail and handles are not released. +} // expected-warning {{Allocated handle is never released; potential resource leak}} + +void checkDoubleRelease01(int tag) { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + if (tag) { + mx_handle_close(sa); + } + mx_handle_close(sa); // expected-warning {{Releasing a previously released handle}} + mx_handle_close(sb); +} + +void checkUseAfterFree01(int tag) { + mx_handle_t sa, sb; + mx_channel_create(0, &sa, &sb); + if (tag) { + mx_handle_close(sa); + } + useHandle(sa); // expected-warning {{Using a previously released handle}} + mx_handle_close(sa); + mx_handle_close(sb); +}