2011-08-05 01:28:06 +08:00
|
|
|
//==--- MacOSKeychainAPIChecker.cpp ------------------------------*- C++ -*-==//
|
2011-08-02 06:40:01 +08:00
|
|
|
//
|
|
|
|
// The LLVM Compiler Infrastructure
|
|
|
|
//
|
|
|
|
// This file is distributed under the University of Illinois Open Source
|
|
|
|
// License. See LICENSE.TXT for details.
|
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// This checker flags misuses of KeyChainAPI. In particular, the password data
|
|
|
|
// allocated/returned by SecKeychainItemCopyContent,
|
|
|
|
// SecKeychainFindGenericPassword, SecKeychainFindInternetPassword functions has
|
|
|
|
// to be freed using a call to SecKeychainItemFreeContent.
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
|
|
|
#include "ClangSACheckers.h"
|
|
|
|
#include "clang/StaticAnalyzer/Core/Checker.h"
|
|
|
|
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
|
2011-08-04 08:26:57 +08:00
|
|
|
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
|
2011-08-02 06:40:01 +08:00
|
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
|
|
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/GRState.h"
|
|
|
|
#include "clang/StaticAnalyzer/Core/PathSensitive/GRStateTrait.h"
|
|
|
|
|
|
|
|
using namespace clang;
|
|
|
|
using namespace ento;
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>,
|
|
|
|
check::PreStmt<ReturnStmt>,
|
|
|
|
check::PostStmt<CallExpr>,
|
|
|
|
check::EndPath > {
|
2011-08-04 08:26:57 +08:00
|
|
|
mutable llvm::OwningPtr<BugType> BT;
|
|
|
|
|
2011-08-02 06:40:01 +08:00
|
|
|
public:
|
|
|
|
void checkPreStmt(const CallExpr *S, CheckerContext &C) const;
|
|
|
|
void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const;
|
|
|
|
void checkPostStmt(const CallExpr *S, CheckerContext &C) const;
|
|
|
|
|
|
|
|
void checkEndPath(EndOfFunctionNodeBuilder &B, ExprEngine &Eng) const;
|
|
|
|
|
|
|
|
private:
|
2011-08-05 01:28:06 +08:00
|
|
|
/// Stores the information about the allocator and deallocator functions -
|
|
|
|
/// these are the functions the checker is tracking.
|
|
|
|
struct ADFunctionInfo {
|
|
|
|
const char* Name;
|
|
|
|
unsigned int Param;
|
|
|
|
unsigned int DeallocatorIdx;
|
|
|
|
};
|
|
|
|
static const unsigned InvalidIdx = 100000;
|
2011-08-05 05:53:01 +08:00
|
|
|
static const unsigned FunctionsToTrackSize = 6;
|
2011-08-05 01:28:06 +08:00
|
|
|
static const ADFunctionInfo FunctionsToTrack[FunctionsToTrackSize];
|
|
|
|
|
|
|
|
/// Given the function name, returns the index of the allocator/deallocator
|
|
|
|
/// function.
|
|
|
|
unsigned getTrackedFunctionIndex(StringRef Name, bool IsAllocator) const;
|
2011-08-04 08:26:57 +08:00
|
|
|
|
|
|
|
inline void initBugType() const {
|
|
|
|
if (!BT)
|
|
|
|
BT.reset(new BugType("Improper use of SecKeychain API", "Mac OS API"));
|
|
|
|
}
|
2011-08-02 06:40:01 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2011-08-05 01:28:06 +08:00
|
|
|
/// AllocationState is a part of the checker specific state together with the
|
|
|
|
/// MemRegion corresponding to the allocated data.
|
|
|
|
struct AllocationState {
|
2011-08-04 08:26:57 +08:00
|
|
|
const Expr *Address;
|
2011-08-05 01:28:06 +08:00
|
|
|
/// The index of the allocator function.
|
|
|
|
unsigned int AllocatorIdx;
|
2011-08-04 08:26:57 +08:00
|
|
|
|
2011-08-05 01:28:06 +08:00
|
|
|
AllocationState(const Expr *E, unsigned int Idx) : Address(E),
|
|
|
|
AllocatorIdx(Idx) {}
|
|
|
|
bool operator==(const AllocationState &X) const {
|
2011-08-04 08:26:57 +08:00
|
|
|
return Address == X.Address;
|
|
|
|
}
|
|
|
|
void Profile(llvm::FoldingSetNodeID &ID) const {
|
|
|
|
ID.AddPointer(Address);
|
2011-08-05 01:28:06 +08:00
|
|
|
ID.AddInteger(AllocatorIdx);
|
2011-08-04 08:26:57 +08:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-08-05 01:28:06 +08:00
|
|
|
/// GRState traits to store the currently allocated (and not yet freed) symbols.
|
|
|
|
typedef llvm::ImmutableMap<const MemRegion*, AllocationState> AllocatedSetTy;
|
2011-08-02 06:40:01 +08:00
|
|
|
|
|
|
|
namespace { struct AllocatedData {}; }
|
|
|
|
namespace clang { namespace ento {
|
|
|
|
template<> struct GRStateTrait<AllocatedData>
|
|
|
|
: public GRStatePartialTrait<AllocatedSetTy > {
|
|
|
|
static void *GDMIndex() { static int index = 0; return &index; }
|
|
|
|
};
|
|
|
|
}}
|
|
|
|
|
2011-08-04 08:26:57 +08:00
|
|
|
static bool isEnclosingFunctionParam(const Expr *E) {
|
|
|
|
E = E->IgnoreParenCasts();
|
|
|
|
if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E)) {
|
|
|
|
const ValueDecl *VD = DRE->getDecl();
|
|
|
|
if (isa<ImplicitParamDecl>(VD) || isa<ParmVarDecl>(VD))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2011-08-05 01:28:06 +08:00
|
|
|
const MacOSKeychainAPIChecker::ADFunctionInfo
|
|
|
|
MacOSKeychainAPIChecker::FunctionsToTrack[FunctionsToTrackSize] = {
|
|
|
|
{"SecKeychainItemCopyContent", 4, 3}, // 0
|
|
|
|
{"SecKeychainFindGenericPassword", 6, 3}, // 1
|
|
|
|
{"SecKeychainFindInternetPassword", 13, 3}, // 2
|
|
|
|
{"SecKeychainItemFreeContent", 1, InvalidIdx}, // 3
|
2011-08-05 05:53:01 +08:00
|
|
|
{"SecKeychainItemCopyAttributesAndData", 5, 5}, // 4
|
|
|
|
{"SecKeychainItemFreeAttributesAndData", 1, InvalidIdx}, // 5
|
2011-08-05 01:28:06 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name,
|
|
|
|
bool IsAllocator) const {
|
|
|
|
for (unsigned I = 0; I < FunctionsToTrackSize; ++I) {
|
|
|
|
ADFunctionInfo FI = FunctionsToTrack[I];
|
|
|
|
if (FI.Name != Name)
|
|
|
|
continue;
|
|
|
|
// Make sure the function is of the right type (allocator vs deallocator).
|
|
|
|
if (IsAllocator && (FI.DeallocatorIdx == InvalidIdx))
|
|
|
|
return InvalidIdx;
|
|
|
|
if (!IsAllocator && (FI.DeallocatorIdx != InvalidIdx))
|
|
|
|
return InvalidIdx;
|
|
|
|
|
|
|
|
return I;
|
|
|
|
}
|
|
|
|
// The function is not tracked.
|
|
|
|
return InvalidIdx;
|
|
|
|
}
|
|
|
|
|
2011-08-05 08:37:00 +08:00
|
|
|
/// Given the address expression, retrieve the value it's pointing to. Assume
|
|
|
|
/// that value is itself an address, and return the corresponding MemRegion.
|
|
|
|
static const MemRegion *getAsPointeeMemoryRegion(const Expr *Expr,
|
|
|
|
CheckerContext &C) {
|
|
|
|
const GRState *State = C.getState();
|
|
|
|
SVal ArgV = State->getSVal(Expr);
|
|
|
|
if (const loc::MemRegionVal *X = dyn_cast<loc::MemRegionVal>(&ArgV)) {
|
|
|
|
StoreManager& SM = C.getStoreManager();
|
|
|
|
const MemRegion *V = SM.Retrieve(State->getStore(), *X).getAsRegion();
|
|
|
|
return V;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-08-02 06:40:01 +08:00
|
|
|
void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE,
|
|
|
|
CheckerContext &C) const {
|
|
|
|
const GRState *State = C.getState();
|
|
|
|
const Expr *Callee = CE->getCallee();
|
|
|
|
SVal L = State->getSVal(Callee);
|
2011-08-05 08:37:00 +08:00
|
|
|
unsigned idx = InvalidIdx;
|
2011-08-02 06:40:01 +08:00
|
|
|
|
|
|
|
const FunctionDecl *funDecl = L.getAsFunctionDecl();
|
|
|
|
if (!funDecl)
|
|
|
|
return;
|
|
|
|
IdentifierInfo *funI = funDecl->getIdentifier();
|
|
|
|
if (!funI)
|
|
|
|
return;
|
|
|
|
StringRef funName = funI->getName();
|
|
|
|
|
2011-08-05 08:37:00 +08:00
|
|
|
// If it is a call to an allocator function, it could be a double allocation.
|
|
|
|
idx = getTrackedFunctionIndex(funName, true);
|
|
|
|
if (idx != InvalidIdx) {
|
|
|
|
const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param);
|
|
|
|
if (const MemRegion *V = getAsPointeeMemoryRegion(ArgExpr, C))
|
|
|
|
if (const AllocationState *AS = State->get<AllocatedData>(V)) {
|
|
|
|
ExplodedNode *N = C.generateSink(State);
|
|
|
|
if (!N)
|
|
|
|
return;
|
|
|
|
initBugType();
|
|
|
|
std::string sbuf;
|
|
|
|
llvm::raw_string_ostream os(sbuf);
|
|
|
|
unsigned int DIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx;
|
|
|
|
os << "Allocated data should be released before another call to "
|
|
|
|
<< "the allocator: missing a call to '"
|
|
|
|
<< FunctionsToTrack[DIdx].Name
|
|
|
|
<< "'.";
|
|
|
|
RangedBugReport *Report = new RangedBugReport(*BT, os.str(), N);
|
|
|
|
Report->addRange(ArgExpr->getSourceRange());
|
|
|
|
C.EmitReport(Report);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is it a call to one of deallocator functions?
|
|
|
|
idx = getTrackedFunctionIndex(funName, false);
|
2011-08-05 01:28:06 +08:00
|
|
|
if (idx == InvalidIdx)
|
2011-08-04 08:31:38 +08:00
|
|
|
return;
|
|
|
|
|
2011-08-05 01:28:06 +08:00
|
|
|
const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param);
|
2011-08-04 08:31:38 +08:00
|
|
|
const MemRegion *Arg = State->getSVal(ArgExpr).getAsRegion();
|
|
|
|
if (!Arg)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// If trying to free data which has not been allocated yet, report as bug.
|
2011-08-05 01:28:06 +08:00
|
|
|
const AllocationState *AS = State->get<AllocatedData>(Arg);
|
|
|
|
if (!AS) {
|
2011-08-04 08:31:38 +08:00
|
|
|
// It is possible that this is a false positive - the argument might
|
|
|
|
// have entered as an enclosing function parameter.
|
|
|
|
if (isEnclosingFunctionParam(ArgExpr))
|
2011-08-02 06:40:01 +08:00
|
|
|
return;
|
2011-08-04 08:26:57 +08:00
|
|
|
|
2011-08-04 08:31:38 +08:00
|
|
|
ExplodedNode *N = C.generateNode(State);
|
|
|
|
if (!N)
|
|
|
|
return;
|
|
|
|
initBugType();
|
|
|
|
RangedBugReport *Report = new RangedBugReport(*BT,
|
|
|
|
"Trying to free data which has not been allocated.", N);
|
|
|
|
Report->addRange(ArgExpr->getSourceRange());
|
|
|
|
C.EmitReport(Report);
|
2011-08-05 01:28:06 +08:00
|
|
|
return;
|
2011-08-02 06:40:01 +08:00
|
|
|
}
|
2011-08-04 08:31:38 +08:00
|
|
|
|
2011-08-05 05:53:01 +08:00
|
|
|
// Check if the proper deallocator is used.
|
|
|
|
unsigned int PDeallocIdx = FunctionsToTrack[AS->AllocatorIdx].DeallocatorIdx;
|
|
|
|
if (PDeallocIdx != idx) {
|
|
|
|
ExplodedNode *N = C.generateSink(State);
|
|
|
|
if (!N)
|
|
|
|
return;
|
|
|
|
initBugType();
|
|
|
|
|
|
|
|
std::string sbuf;
|
|
|
|
llvm::raw_string_ostream os(sbuf);
|
|
|
|
os << "Allocator doesn't match the deallocator: '"
|
|
|
|
<< FunctionsToTrack[PDeallocIdx].Name << "' should be used.";
|
|
|
|
RangedBugReport *Report = new RangedBugReport(*BT, os.str(), N);
|
|
|
|
Report->addRange(ArgExpr->getSourceRange());
|
|
|
|
C.EmitReport(Report);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-08-05 08:37:00 +08:00
|
|
|
// If a value has been freed, remove it from the list and continue exploring
|
|
|
|
// from the new state.
|
2011-08-04 08:31:38 +08:00
|
|
|
State = State->remove<AllocatedData>(Arg);
|
|
|
|
C.addTransition(State);
|
2011-08-02 06:40:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void MacOSKeychainAPIChecker::checkPostStmt(const CallExpr *CE,
|
|
|
|
CheckerContext &C) const {
|
|
|
|
const GRState *State = C.getState();
|
|
|
|
const Expr *Callee = CE->getCallee();
|
|
|
|
SVal L = State->getSVal(Callee);
|
|
|
|
|
|
|
|
const FunctionDecl *funDecl = L.getAsFunctionDecl();
|
|
|
|
if (!funDecl)
|
|
|
|
return;
|
|
|
|
IdentifierInfo *funI = funDecl->getIdentifier();
|
|
|
|
if (!funI)
|
|
|
|
return;
|
|
|
|
StringRef funName = funI->getName();
|
|
|
|
|
|
|
|
// If a value has been allocated, add it to the set for tracking.
|
2011-08-05 01:28:06 +08:00
|
|
|
unsigned idx = getTrackedFunctionIndex(funName, true);
|
|
|
|
if (idx == InvalidIdx)
|
2011-08-04 08:31:38 +08:00
|
|
|
return;
|
|
|
|
|
2011-08-05 01:28:06 +08:00
|
|
|
const Expr *ArgExpr = CE->getArg(FunctionsToTrack[idx].Param);
|
2011-08-05 08:37:00 +08:00
|
|
|
if (const MemRegion *V = getAsPointeeMemoryRegion(ArgExpr, C)) {
|
|
|
|
// If the argument points to something that's not a region, it can be:
|
2011-08-04 08:31:38 +08:00
|
|
|
// - unknown (cannot reason about it)
|
|
|
|
// - undefined (already reported by other checker)
|
2011-08-05 01:28:06 +08:00
|
|
|
// - constant (null - should not be tracked,
|
|
|
|
// other constant will generate a compiler warning)
|
2011-08-04 08:31:38 +08:00
|
|
|
// - goto (should be reported by other checker)
|
|
|
|
|
|
|
|
// We only need to track the value if the function returned noErr(0), so
|
2011-08-05 05:53:01 +08:00
|
|
|
// bind the return value of the function to 0 and proceed from the no error
|
|
|
|
// state.
|
2011-08-04 08:31:38 +08:00
|
|
|
SValBuilder &Builder = C.getSValBuilder();
|
2011-08-05 05:53:01 +08:00
|
|
|
SVal ZeroVal = Builder.makeIntVal(0, CE->getCallReturnType());
|
|
|
|
const GRState *NoErr = State->BindExpr(CE, ZeroVal);
|
2011-08-05 08:37:00 +08:00
|
|
|
// Add the symbolic value V, which represents the location of the allocated
|
|
|
|
// data, to the set.
|
2011-08-05 05:53:01 +08:00
|
|
|
NoErr = NoErr->set<AllocatedData>(V, AllocationState(ArgExpr, idx));
|
|
|
|
assert(NoErr);
|
|
|
|
C.addTransition(NoErr);
|
|
|
|
|
|
|
|
// Generate a transition to explore the state space when there is an error.
|
|
|
|
// In this case, we do not track the allocated data.
|
|
|
|
SVal ReturnedError = Builder.evalBinOpNN(State, BO_NE,
|
|
|
|
cast<NonLoc>(ZeroVal),
|
|
|
|
cast<NonLoc>(State->getSVal(CE)),
|
|
|
|
CE->getCallReturnType());
|
|
|
|
const GRState *Err = State->assume(cast<NonLoc>(ReturnedError), true);
|
|
|
|
assert(Err);
|
|
|
|
C.addTransition(Err);
|
2011-08-02 06:40:01 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MacOSKeychainAPIChecker::checkPreStmt(const ReturnStmt *S,
|
|
|
|
CheckerContext &C) const {
|
|
|
|
const Expr *retExpr = S->getRetValue();
|
|
|
|
if (!retExpr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// Check if the value is escaping through the return.
|
|
|
|
const GRState *state = C.getState();
|
2011-08-04 08:26:57 +08:00
|
|
|
const MemRegion *V = state->getSVal(retExpr).getAsRegion();
|
2011-08-02 06:40:01 +08:00
|
|
|
if (!V)
|
|
|
|
return;
|
2011-08-04 08:26:57 +08:00
|
|
|
state = state->remove<AllocatedData>(V);
|
2011-08-02 06:40:01 +08:00
|
|
|
|
2011-08-04 08:26:57 +08:00
|
|
|
// Proceed from the new state.
|
|
|
|
C.addTransition(state);
|
2011-08-02 06:40:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void MacOSKeychainAPIChecker::checkEndPath(EndOfFunctionNodeBuilder &B,
|
2011-08-04 08:26:57 +08:00
|
|
|
ExprEngine &Eng) const {
|
2011-08-02 06:40:01 +08:00
|
|
|
const GRState *state = B.getState();
|
|
|
|
AllocatedSetTy AS = state->get<AllocatedData>();
|
2011-08-04 08:26:57 +08:00
|
|
|
ExplodedNode *N = B.generateNode(state);
|
|
|
|
if (!N)
|
|
|
|
return;
|
|
|
|
initBugType();
|
2011-08-02 06:40:01 +08:00
|
|
|
|
|
|
|
// Anything which has been allocated but not freed (nor escaped) will be
|
|
|
|
// found here, so report it.
|
2011-08-04 08:26:57 +08:00
|
|
|
for (AllocatedSetTy::iterator I = AS.begin(), E = AS.end(); I != E; ++I ) {
|
2011-08-05 05:53:01 +08:00
|
|
|
const ADFunctionInfo &FI = FunctionsToTrack[I->second.AllocatorIdx];
|
|
|
|
|
|
|
|
std::string sbuf;
|
|
|
|
llvm::raw_string_ostream os(sbuf);
|
|
|
|
os << "Allocated data is not released: missing a call to '"
|
|
|
|
<< FunctionsToTrack[FI.DeallocatorIdx].Name << "'.";
|
|
|
|
RangedBugReport *Report = new RangedBugReport(*BT, os.str(), N);
|
2011-08-04 08:26:57 +08:00
|
|
|
// TODO: The report has to mention the expression which contains the
|
|
|
|
// allocated content as well as the point at which it has been allocated.
|
|
|
|
// Currently, the next line is useless.
|
|
|
|
Report->addRange(I->second.Address->getSourceRange());
|
|
|
|
Eng.getBugReporter().EmitReport(Report);
|
2011-08-02 06:40:01 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void ento::registerMacOSKeychainAPIChecker(CheckerManager &mgr) {
|
|
|
|
mgr.registerChecker<MacOSKeychainAPIChecker>();
|
|
|
|
}
|