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,928 @@ +//== 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); } +}; + +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; + + ProgramStateRef conjureFailedState(const CallExpr *CE, ProgramStateRef State, + 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; + + int64_t getArgValueFromExpr(const Expr *ArgExpr, ProgramStateRef State, + const LocationContext *LCtx, + int64_t DefaultVal) const; + + SymbolRef getParentSymbolFromElementRegion(const ElementRegion *EleRegion, + ProgramStateRef State) const; + + ProgramStateRef allocateSingleHandle(const Expr *ArgExpr, + const LocationContext *LCtx, + ProgramStateRef State) const; + + ProgramStateRef allocateHandlesForArray(int HandleCount, + ProgramStateRef State, + CheckerContext &Ctx, QualType EleType, + SymbolRef ParentSymbol, + const SubRegion *SuperRegion) const; + + ProgramStateRef releaseHandlesForArray(int HandleCount, const CallExpr *CE, + ProgramStateRef State, + CheckerContext &Ctx, QualType EleType, + SymbolRef ParentSymbol, + const SubRegion *SuperRegion) const; + + ProgramStateRef releaseAssociatedHandles(const CallExpr *CE, + ProgramStateRef State, + CheckerContext &Ctx, + SymbolRef ParentSymbol) const; + + // Return true only if it is certain that Sym will be Zero + static bool CheckSymbolConstraintToZero(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 + // An annotation based matching system is work in progress. + 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; +} + +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()); + } + + 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); + Optional DSVal = ExprSVal.getAs(); + if (DSVal.hasValue()) + return !State->assume(DSVal.getValue(), true); + + return false; +} + +void MagentaHandleChecker::checkPreCall(const CallEvent &Call, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + if (CheckCallSignature(Call) == MX_HANDLE_CLOSE) + processMxHandleClose(State, Call, Ctx); +} + +void MagentaHandleChecker::checkPostCall(const CallEvent &Call, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + if (CheckCallSignature(Call) == UNRELATED_CALL) + processUninlinedCalls(State, Call, Ctx); +} + +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); + } + 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 || SFCtx->inTopFrame()) + 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 + // Annotation based system is work in progress. + 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); +} + +// This function process calls to syscall +// 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); +bool MagentaHandleChecker::processMxChannelRead(const CallExpr *CE, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + const LocationContext *LCtx = Ctx.getLocationContext(); + SValBuilder &svalBuilder = Ctx.getSValBuilder(); + + // Check if handle related args are nullptr or 0. If so, 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; + + // this syscall may fail during runtime, bifurcate the state here. + ProgramStateRef FailedState = conjureFailedState(CE, State, Ctx); + + // 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); + // Conjure a successful state with return value is 0 + SVal StatusSVal = svalBuilder.makeIntVal(llvm::APSInt::get(0)); + State = State->BindExpr(CE, LCtx, StatusSVal); + + const MemRegion *MemHandleBuf = + State->getSVal(HandleBufExpr, LCtx).getAsRegion(); + if (!MemHandleBuf) + return false; + + // Determine how many handles should be allocated, it may have following + // cases: + // 1. handlebuf is a pointer to a local variable, allocate 1 handle. + // 2. handlebuf is an array and InputSizeExpr can be resolved to a concrete + // int, denoted as inputsize. If 0 < inputsize <= 4, allocate + // exact number of handles as indicated by inputsize. If inputsize <= 0, + // consider it as an error and fallback to conservativeEvalCall. If + // inputsize > 4, assign 4 to inputsize. + // 3. handlebuf is an array and InputSizeExpr cannot be resolved to a concrete + // integer. In this case, assume inputsize is 4 and do the same thing as + // condition 2 + int64_t ExtVal = getArgValueFromExpr(InputSizeExpr, State, LCtx, 4); + if (ExtVal < 0) + return false; + if (!isa(MemHandleBuf)) { + if (isa(MemHandleBuf)) { + // It is condition 1 + State = allocateSingleHandle(HandleBufExpr, LCtx, State); + ExtVal = 1; + } else { + // Unknown condition + return false; + } + } else { + if (ExtVal > 4) + ExtVal = 4; + // It is condition 2 or 3 + const ElementRegion *EleRegion = dyn_cast(MemHandleBuf); + SymbolRef ParentSymbol = getParentSymbolFromElementRegion(EleRegion, State); + if (!ParentSymbol) + return false; + + const SubRegion *SuperRegionSub = + dyn_cast(EleRegion->getSuperRegion()); + // ElementRegion is a child of TypedValueRegion. Get the type. + QualType HandleType = cast(MemHandleBuf)->getValueType(); + State = allocateHandlesForArray(ExtVal, State, Ctx, HandleType, + ParentSymbol, SuperRegionSub); + if (!State) + return false; + } + // 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; +} + +// This function process calls to syscall +// mx_status_t mx_channel_create(uint32_t options, +// mx_handle_t* out0, mx_handle_t* out1); +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 + ProgramStateRef FailedState = conjureFailedState(CE, State, Ctx); + + // Invalidate handle pointers and conjure a successful state with status = 0 + SmallVector invalidateSVal; + 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); + + State = allocateSingleHandle(Handle1Expr, LCtx, State); + if (!State) + return false; + + State = allocateSingleHandle(Handle2Expr, LCtx, State); + if (!State) + return false; + + Ctx.addTransition(State); + Ctx.addTransition(FailedState); + return true; +} + +ProgramStateRef +MagentaHandleChecker::allocateSingleHandle(const Expr *ArgExpr, + const LocationContext *LCtx, + ProgramStateRef State) const { + if (!State || !LCtx) + return nullptr; + SVal ArgSVal = State->getSVal(ArgExpr, LCtx); + // It is a pointer, so get the mem region first. + const MemRegion *MemHandle = ArgSVal.getAsRegion(); + if (!MemHandle) + return nullptr; + + SVal SValHandle = State->getSVal(MemHandle); + SymbolRef SymHandle = SValHandle.getAsSymbol(); + if (!SymHandle) + return nullptr; + + DefinedOrUnknownSVal DSValHandle = SValHandle.castAs(); + if (DSValHandle.getBaseKind() == SVal::NonLocKind) + // Allocated handle should be larger than 0 + State->assumeInclusiveRange(DSValHandle, llvm::APSInt::get(1), + llvm::APSInt::get(INT_MAX), true); + + HandleState HS = HandleState::getAllocated(); + return State->set(SymHandle, HS); +} + +ProgramStateRef MagentaHandleChecker::allocateHandlesForArray( + int HandleCount, ProgramStateRef State, CheckerContext &Ctx, + QualType EleType, SymbolRef ParentSymbol, + const SubRegion *SuperRegion) const { + + SValBuilder &Bldr = Ctx.getSValBuilder(); + MemRegionManager &MemRegionMgr = Bldr.getRegionManager(); + ASTContext &ASTCtx = State->getStateManager().getContext(); + for (int i = 0; i < HandleCount; ++i) { + NonLoc AryIndex = Bldr.makeArrayIndex(i); + const ElementRegion *CurElementRegion = + MemRegionMgr.getElementRegion(EleType, AryIndex, SuperRegion, ASTCtx); + if (!CurElementRegion) + return nullptr; + // Build derived SVal for elements in the handle buffer + DefinedOrUnknownSVal CurSVal = + Bldr.getDerivedRegionValueSymbolVal(ParentSymbol, CurElementRegion); + SymbolRef CurSymbol = CurSVal.getAsSymbol(); + if (!CurSymbol) + return nullptr; + // 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(INT_MAX), true); + HandleState HS = HandleState::getAllocated(); + State = State->set(CurSymbol, HS); + } + return State; +} + +// This function process calls to syscall +// 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) +bool MagentaHandleChecker::processMxChannelWrite(const CallExpr *CE, + CheckerContext &Ctx) const { + ProgramStateRef State = Ctx.getState(); + const LocationContext *LCtx = Ctx.getLocationContext(); + SValBuilder &svalBuilder = Ctx.getSValBuilder(); + + // 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; + + // this syscall may fail during runtime, bifurcate the state here. + ProgramStateRef FailedState = conjureFailedState(CE, State, Ctx); + // Conjure a successful state + SVal StatusSVal = svalBuilder.makeIntVal(llvm::APSInt::get(0)); + State = State->BindExpr(CE, LCtx, StatusSVal); + + // Determine how many handles should be released. it may have following + // cases: + // 1. handlebuf is a pointer to a local mx_handle_t variable, only 1 handle + // should be released + // 2. handlebuf is an array and InputSizeExpr can be resolved to a concrete + // int, let's denote it as inputsize. If 0 < inputsize <= 4, release + // exact number of handles as indicated by inputsize. If inputsize <= 0, + // consider it as an error and fallback to conservativeEvalCall. If + // inputsize > 4 it is case 3. + // 3. handlebuf is an array and inputsize is larger than 4 or cannot be + // resolved to concrete int, release all handles that are associated with + // handlebuf. + int64_t ExtVal = getArgValueFromExpr(InputSizeExpr, State, LCtx, -1); + // Retrive MemRegion of the handle buffer + SVal HandleBufSValOrig = State->getSVal(HandleBufExpr, LCtx); + const MemRegion *MemHandleBuf = HandleBufSValOrig.getAsRegion(); + bool TransitionHappened = false; + if (!isa(MemHandleBuf)) { + if (isa(MemHandleBuf)) { + // It is condition 1 + ExtVal = 1; + SVal HandleBufSVal = State->getSVal(MemHandleBuf); + SymbolRef SymHandleBuf = HandleBufSVal.getAsSymbol(); + if (!SymHandleBuf) + return false; + const HandleState *HS = State->get(SymHandleBuf); + if (HS) { + if (HS->isReleased()) { + reportDoubleRelease(SymHandleBuf, CE->getSourceRange(), Ctx); + } else { + State = + State->set(SymHandleBuf, HandleState::getReleased()); + } + } + } else { + // Unknown condition + return false; + } + } else { + // Condition 2 or 3. + const ElementRegion *EleRegion = dyn_cast(MemHandleBuf); + SymbolRef ParentSymbol = getParentSymbolFromElementRegion(EleRegion, State); + if (!ParentSymbol) + return false; + const SubRegion *SuperRegionSub = + dyn_cast(EleRegion->getSuperRegion()); + QualType HandleType = cast(MemHandleBuf)->getValueType(); + + if (ExtVal < 0 || ExtVal > 5) { + // Condition 2 + ProgramStateRef NewState = + releaseAssociatedHandles(CE, State, Ctx, ParentSymbol); + if (!NewState && NewState != State) { + State = NewState; + TransitionHappened = true; + } + } else { + // Condition 3 + ProgramStateRef NewState = releaseHandlesForArray( + ExtVal, CE, State, Ctx, HandleType, ParentSymbol, SuperRegionSub); + if (!NewState) + return false; + if (NewState != State) + TransitionHappened = true; + State = NewState; + } + } + + if (TransitionHappened) + Ctx.addTransition(State); + Ctx.addTransition(FailedState); + return true; +} + +ProgramStateRef MagentaHandleChecker::releaseHandlesForArray( + int HandleCount, const CallExpr *CE, ProgramStateRef State, + CheckerContext &Ctx, QualType EleType, SymbolRef ParentSymbol, + const SubRegion *SuperRegion) const { + SValBuilder &Bldr = Ctx.getSValBuilder(); + MemRegionManager &MemRegionMgr = Bldr.getRegionManager(); + ASTContext &ASTCtx = State->getStateManager().getContext(); + for (int i = 0; i < HandleCount; ++i) { + NonLoc AryIndex = Bldr.makeArrayIndex(i); + const ElementRegion *CurElementRegion = + MemRegionMgr.getElementRegion(EleType, AryIndex, SuperRegion, ASTCtx); + if (!CurElementRegion) + return nullptr; + // Build derived SVal for elements in the handle buffer + DefinedOrUnknownSVal CurSVal = + Bldr.getDerivedRegionValueSymbolVal(ParentSymbol, CurElementRegion); + SymbolRef CurSymbol = CurSVal.getAsSymbol(); + if (!CurSymbol) + return nullptr; + + const HandleState *SymState = State->get(CurSymbol); + if (!SymState) + continue; + + if (SymState->isReleased()) { + reportDoubleRelease(CurSymbol, CE->getSourceRange(), Ctx); + } else { + State = State->set(CurSymbol, HandleState::getReleased()); + } + } + return State; +} + +ProgramStateRef MagentaHandleChecker::releaseAssociatedHandles( + const CallExpr *CE, ProgramStateRef State, CheckerContext &Ctx, + SymbolRef ParentSymbol) const { + 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()); + } + } + } + return State; +} + +SymbolRef MagentaHandleChecker::getParentSymbolFromElementRegion( + const ElementRegion *EleRegion, ProgramStateRef State) const { + if (!EleRegion || !State) + return nullptr; + + const SubRegion *SuperRegionSub = + dyn_cast(EleRegion->getSuperRegion()); + if (!SuperRegionSub) + return nullptr; + + SVal HandleBufSVal = State->getSVal(EleRegion); + SymbolRef SymHandleBuf = HandleBufSVal.getAsSymbol(); + if (!SymHandleBuf) + return nullptr; + + // Rare condition. There are some cases that the member of array is not + // SymbolDerived types. + if (!isa(SymHandleBuf)) + return nullptr; + + SymbolRef ParentSymbol = + dyn_cast(SymHandleBuf)->getParentSymbol(); + return ParentSymbol; +} +// This function process calls to syscall +// mx_status_t mx_handle_close(mx_handle_t handle); +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); +} + +// When a magenta syscall failed. It will return an integer less than 0. +ProgramStateRef MagentaHandleChecker::conjureFailedState( + const CallExpr *CE, ProgramStateRef State, CheckerContext &Ctx) const { + const LocationContext *LCtx = Ctx.getLocationContext(); + 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); + return FailedState; +} + +int64_t MagentaHandleChecker::getArgValueFromExpr(const Expr *ArgExpr, + ProgramStateRef State, + const LocationContext *LCtx, + int64_t DefaultVal) const { + int64_t ExtVal = DefaultVal; + SVal InputSizeSVal = State->getSVal(ArgExpr, LCtx); + 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(); + } + return ExtVal; +} + +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); +}