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