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 | |