1*0b57cec5SDimitry Andric //== ObjCContainersChecker.cpp - Path sensitive checker for CFArray *- C++ -*=// 2*0b57cec5SDimitry Andric // 3*0b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4*0b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 5*0b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6*0b57cec5SDimitry Andric // 7*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 8*0b57cec5SDimitry Andric // 9*0b57cec5SDimitry Andric // Performs path sensitive checks of Core Foundation static containers like 10*0b57cec5SDimitry Andric // CFArray. 11*0b57cec5SDimitry Andric // 1) Check for buffer overflows: 12*0b57cec5SDimitry Andric // In CFArrayGetArrayAtIndex( myArray, index), if the index is outside the 13*0b57cec5SDimitry Andric // index space of theArray (0 to N-1 inclusive (where N is the count of 14*0b57cec5SDimitry Andric // theArray), the behavior is undefined. 15*0b57cec5SDimitry Andric // 16*0b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 17*0b57cec5SDimitry Andric 18*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 19*0b57cec5SDimitry Andric #include "clang/AST/ParentMap.h" 20*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 21*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h" 22*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/CheckerManager.h" 23*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 24*0b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 25*0b57cec5SDimitry Andric 26*0b57cec5SDimitry Andric using namespace clang; 27*0b57cec5SDimitry Andric using namespace ento; 28*0b57cec5SDimitry Andric 29*0b57cec5SDimitry Andric namespace { 30*0b57cec5SDimitry Andric class ObjCContainersChecker : public Checker< check::PreStmt<CallExpr>, 31*0b57cec5SDimitry Andric check::PostStmt<CallExpr>, 32*0b57cec5SDimitry Andric check::PointerEscape> { 33*0b57cec5SDimitry Andric mutable std::unique_ptr<BugType> BT; 34*0b57cec5SDimitry Andric inline void initBugType() const { 35*0b57cec5SDimitry Andric if (!BT) 36*0b57cec5SDimitry Andric BT.reset(new BugType(this, "CFArray API", 37*0b57cec5SDimitry Andric categories::CoreFoundationObjectiveC)); 38*0b57cec5SDimitry Andric } 39*0b57cec5SDimitry Andric 40*0b57cec5SDimitry Andric inline SymbolRef getArraySym(const Expr *E, CheckerContext &C) const { 41*0b57cec5SDimitry Andric SVal ArrayRef = C.getSVal(E); 42*0b57cec5SDimitry Andric SymbolRef ArraySym = ArrayRef.getAsSymbol(); 43*0b57cec5SDimitry Andric return ArraySym; 44*0b57cec5SDimitry Andric } 45*0b57cec5SDimitry Andric 46*0b57cec5SDimitry Andric void addSizeInfo(const Expr *Array, const Expr *Size, 47*0b57cec5SDimitry Andric CheckerContext &C) const; 48*0b57cec5SDimitry Andric 49*0b57cec5SDimitry Andric public: 50*0b57cec5SDimitry Andric /// A tag to id this checker. 51*0b57cec5SDimitry Andric static void *getTag() { static int Tag; return &Tag; } 52*0b57cec5SDimitry Andric 53*0b57cec5SDimitry Andric void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; 54*0b57cec5SDimitry Andric void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; 55*0b57cec5SDimitry Andric ProgramStateRef checkPointerEscape(ProgramStateRef State, 56*0b57cec5SDimitry Andric const InvalidatedSymbols &Escaped, 57*0b57cec5SDimitry Andric const CallEvent *Call, 58*0b57cec5SDimitry Andric PointerEscapeKind Kind) const; 59*0b57cec5SDimitry Andric 60*0b57cec5SDimitry Andric void printState(raw_ostream &OS, ProgramStateRef State, 61*0b57cec5SDimitry Andric const char *NL, const char *Sep) const; 62*0b57cec5SDimitry Andric }; 63*0b57cec5SDimitry Andric } // end anonymous namespace 64*0b57cec5SDimitry Andric 65*0b57cec5SDimitry Andric // ProgramState trait - a map from array symbol to its state. 66*0b57cec5SDimitry Andric REGISTER_MAP_WITH_PROGRAMSTATE(ArraySizeMap, SymbolRef, DefinedSVal) 67*0b57cec5SDimitry Andric 68*0b57cec5SDimitry Andric void ObjCContainersChecker::addSizeInfo(const Expr *Array, const Expr *Size, 69*0b57cec5SDimitry Andric CheckerContext &C) const { 70*0b57cec5SDimitry Andric ProgramStateRef State = C.getState(); 71*0b57cec5SDimitry Andric SVal SizeV = C.getSVal(Size); 72*0b57cec5SDimitry Andric // Undefined is reported by another checker. 73*0b57cec5SDimitry Andric if (SizeV.isUnknownOrUndef()) 74*0b57cec5SDimitry Andric return; 75*0b57cec5SDimitry Andric 76*0b57cec5SDimitry Andric // Get the ArrayRef symbol. 77*0b57cec5SDimitry Andric SVal ArrayRef = C.getSVal(Array); 78*0b57cec5SDimitry Andric SymbolRef ArraySym = ArrayRef.getAsSymbol(); 79*0b57cec5SDimitry Andric if (!ArraySym) 80*0b57cec5SDimitry Andric return; 81*0b57cec5SDimitry Andric 82*0b57cec5SDimitry Andric C.addTransition( 83*0b57cec5SDimitry Andric State->set<ArraySizeMap>(ArraySym, SizeV.castAs<DefinedSVal>())); 84*0b57cec5SDimitry Andric } 85*0b57cec5SDimitry Andric 86*0b57cec5SDimitry Andric void ObjCContainersChecker::checkPostStmt(const CallExpr *CE, 87*0b57cec5SDimitry Andric CheckerContext &C) const { 88*0b57cec5SDimitry Andric StringRef Name = C.getCalleeName(CE); 89*0b57cec5SDimitry Andric if (Name.empty() || CE->getNumArgs() < 1) 90*0b57cec5SDimitry Andric return; 91*0b57cec5SDimitry Andric 92*0b57cec5SDimitry Andric // Add array size information to the state. 93*0b57cec5SDimitry Andric if (Name.equals("CFArrayCreate")) { 94*0b57cec5SDimitry Andric if (CE->getNumArgs() < 3) 95*0b57cec5SDimitry Andric return; 96*0b57cec5SDimitry Andric // Note, we can visit the Create method in the post-visit because 97*0b57cec5SDimitry Andric // the CFIndex parameter is passed in by value and will not be invalidated 98*0b57cec5SDimitry Andric // by the call. 99*0b57cec5SDimitry Andric addSizeInfo(CE, CE->getArg(2), C); 100*0b57cec5SDimitry Andric return; 101*0b57cec5SDimitry Andric } 102*0b57cec5SDimitry Andric 103*0b57cec5SDimitry Andric if (Name.equals("CFArrayGetCount")) { 104*0b57cec5SDimitry Andric addSizeInfo(CE->getArg(0), CE, C); 105*0b57cec5SDimitry Andric return; 106*0b57cec5SDimitry Andric } 107*0b57cec5SDimitry Andric } 108*0b57cec5SDimitry Andric 109*0b57cec5SDimitry Andric void ObjCContainersChecker::checkPreStmt(const CallExpr *CE, 110*0b57cec5SDimitry Andric CheckerContext &C) const { 111*0b57cec5SDimitry Andric StringRef Name = C.getCalleeName(CE); 112*0b57cec5SDimitry Andric if (Name.empty() || CE->getNumArgs() < 2) 113*0b57cec5SDimitry Andric return; 114*0b57cec5SDimitry Andric 115*0b57cec5SDimitry Andric // Check the array access. 116*0b57cec5SDimitry Andric if (Name.equals("CFArrayGetValueAtIndex")) { 117*0b57cec5SDimitry Andric ProgramStateRef State = C.getState(); 118*0b57cec5SDimitry Andric // Retrieve the size. 119*0b57cec5SDimitry Andric // Find out if we saw this array symbol before and have information about 120*0b57cec5SDimitry Andric // it. 121*0b57cec5SDimitry Andric const Expr *ArrayExpr = CE->getArg(0); 122*0b57cec5SDimitry Andric SymbolRef ArraySym = getArraySym(ArrayExpr, C); 123*0b57cec5SDimitry Andric if (!ArraySym) 124*0b57cec5SDimitry Andric return; 125*0b57cec5SDimitry Andric 126*0b57cec5SDimitry Andric const DefinedSVal *Size = State->get<ArraySizeMap>(ArraySym); 127*0b57cec5SDimitry Andric 128*0b57cec5SDimitry Andric if (!Size) 129*0b57cec5SDimitry Andric return; 130*0b57cec5SDimitry Andric 131*0b57cec5SDimitry Andric // Get the index. 132*0b57cec5SDimitry Andric const Expr *IdxExpr = CE->getArg(1); 133*0b57cec5SDimitry Andric SVal IdxVal = C.getSVal(IdxExpr); 134*0b57cec5SDimitry Andric if (IdxVal.isUnknownOrUndef()) 135*0b57cec5SDimitry Andric return; 136*0b57cec5SDimitry Andric DefinedSVal Idx = IdxVal.castAs<DefinedSVal>(); 137*0b57cec5SDimitry Andric 138*0b57cec5SDimitry Andric // Now, check if 'Idx in [0, Size-1]'. 139*0b57cec5SDimitry Andric const QualType T = IdxExpr->getType(); 140*0b57cec5SDimitry Andric ProgramStateRef StInBound = State->assumeInBound(Idx, *Size, true, T); 141*0b57cec5SDimitry Andric ProgramStateRef StOutBound = State->assumeInBound(Idx, *Size, false, T); 142*0b57cec5SDimitry Andric if (StOutBound && !StInBound) { 143*0b57cec5SDimitry Andric ExplodedNode *N = C.generateErrorNode(StOutBound); 144*0b57cec5SDimitry Andric if (!N) 145*0b57cec5SDimitry Andric return; 146*0b57cec5SDimitry Andric initBugType(); 147*0b57cec5SDimitry Andric auto R = llvm::make_unique<BugReport>(*BT, "Index is out of bounds", N); 148*0b57cec5SDimitry Andric R->addRange(IdxExpr->getSourceRange()); 149*0b57cec5SDimitry Andric bugreporter::trackExpressionValue(N, IdxExpr, *R, 150*0b57cec5SDimitry Andric /*EnableNullFPSuppression=*/false); 151*0b57cec5SDimitry Andric C.emitReport(std::move(R)); 152*0b57cec5SDimitry Andric return; 153*0b57cec5SDimitry Andric } 154*0b57cec5SDimitry Andric } 155*0b57cec5SDimitry Andric } 156*0b57cec5SDimitry Andric 157*0b57cec5SDimitry Andric ProgramStateRef 158*0b57cec5SDimitry Andric ObjCContainersChecker::checkPointerEscape(ProgramStateRef State, 159*0b57cec5SDimitry Andric const InvalidatedSymbols &Escaped, 160*0b57cec5SDimitry Andric const CallEvent *Call, 161*0b57cec5SDimitry Andric PointerEscapeKind Kind) const { 162*0b57cec5SDimitry Andric for (const auto &Sym : Escaped) { 163*0b57cec5SDimitry Andric // When a symbol for a mutable array escapes, we can't reason precisely 164*0b57cec5SDimitry Andric // about its size any more -- so remove it from the map. 165*0b57cec5SDimitry Andric // Note that we aren't notified here when a CFMutableArrayRef escapes as a 166*0b57cec5SDimitry Andric // CFArrayRef. This is because CFArrayRef is typedef'd as a pointer to a 167*0b57cec5SDimitry Andric // const-qualified type. 168*0b57cec5SDimitry Andric State = State->remove<ArraySizeMap>(Sym); 169*0b57cec5SDimitry Andric } 170*0b57cec5SDimitry Andric return State; 171*0b57cec5SDimitry Andric } 172*0b57cec5SDimitry Andric 173*0b57cec5SDimitry Andric void ObjCContainersChecker::printState(raw_ostream &OS, ProgramStateRef State, 174*0b57cec5SDimitry Andric const char *NL, const char *Sep) const { 175*0b57cec5SDimitry Andric ArraySizeMapTy Map = State->get<ArraySizeMap>(); 176*0b57cec5SDimitry Andric if (Map.isEmpty()) 177*0b57cec5SDimitry Andric return; 178*0b57cec5SDimitry Andric 179*0b57cec5SDimitry Andric OS << Sep << "ObjC container sizes :" << NL; 180*0b57cec5SDimitry Andric for (auto I : Map) { 181*0b57cec5SDimitry Andric OS << I.first << " : " << I.second << NL; 182*0b57cec5SDimitry Andric } 183*0b57cec5SDimitry Andric } 184*0b57cec5SDimitry Andric 185*0b57cec5SDimitry Andric /// Register checker. 186*0b57cec5SDimitry Andric void ento::registerObjCContainersChecker(CheckerManager &mgr) { 187*0b57cec5SDimitry Andric mgr.registerChecker<ObjCContainersChecker>(); 188*0b57cec5SDimitry Andric } 189*0b57cec5SDimitry Andric 190*0b57cec5SDimitry Andric bool ento::shouldRegisterObjCContainersChecker(const LangOptions &LO) { 191*0b57cec5SDimitry Andric return true; 192*0b57cec5SDimitry Andric } 193