1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
15 | #include "clang/AST/CharUnits.h" |
16 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
17 | #include "clang/StaticAnalyzer/Core/Checker.h" |
18 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
19 | #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" |
20 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
21 | #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" |
22 | #include "llvm/ADT/SmallString.h" |
23 | #include "llvm/Support/raw_ostream.h" |
24 | |
25 | using namespace clang; |
26 | using namespace ento; |
27 | |
28 | namespace { |
29 | class ArrayBoundCheckerV2 : |
30 | public Checker<check::Location> { |
31 | mutable std::unique_ptr<BuiltinBug> BT; |
32 | |
33 | enum OOB_Kind { OOB_Precedes, OOB_Excedes, OOB_Tainted }; |
34 | |
35 | void reportOOB(CheckerContext &C, ProgramStateRef errorState, OOB_Kind kind, |
36 | std::unique_ptr<BugReporterVisitor> Visitor = nullptr) const; |
37 | |
38 | public: |
39 | void checkLocation(SVal l, bool isLoad, const Stmt*S, |
40 | CheckerContext &C) const; |
41 | }; |
42 | |
43 | |
44 | class RegionRawOffsetV2 { |
45 | private: |
46 | const SubRegion *baseRegion; |
47 | SVal byteOffset; |
48 | |
49 | RegionRawOffsetV2() |
50 | : baseRegion(nullptr), byteOffset(UnknownVal()) {} |
51 | |
52 | public: |
53 | RegionRawOffsetV2(const SubRegion* base, SVal offset) |
54 | : baseRegion(base), byteOffset(offset) {} |
55 | |
56 | NonLoc getByteOffset() const { return byteOffset.castAs<NonLoc>(); } |
57 | const SubRegion *getRegion() const { return baseRegion; } |
58 | |
59 | static RegionRawOffsetV2 computeOffset(ProgramStateRef state, |
60 | SValBuilder &svalBuilder, |
61 | SVal location); |
62 | |
63 | void dump() const; |
64 | void dumpToStream(raw_ostream &os) const; |
65 | }; |
66 | } |
67 | |
68 | static SVal computeExtentBegin(SValBuilder &svalBuilder, |
69 | const MemRegion *region) { |
70 | const MemSpaceRegion *SR = region->getMemorySpace(); |
71 | if (SR->getKind() == MemRegion::UnknownSpaceRegionKind) |
72 | return UnknownVal(); |
73 | else |
74 | return svalBuilder.makeZeroArrayIndex(); |
75 | } |
76 | |
77 | |
78 | |
79 | |
80 | |
81 | static std::pair<NonLoc, nonloc::ConcreteInt> |
82 | getSimplifiedOffsets(NonLoc offset, nonloc::ConcreteInt extent, |
83 | SValBuilder &svalBuilder) { |
84 | Optional<nonloc::SymbolVal> SymVal = offset.getAs<nonloc::SymbolVal>(); |
85 | if (SymVal && SymVal->isExpression()) { |
86 | if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(SymVal->getSymbol())) { |
87 | llvm::APSInt constant = |
88 | APSIntType(extent.getValue()).convert(SIE->getRHS()); |
89 | switch (SIE->getOpcode()) { |
90 | case BO_Mul: |
91 | |
92 | |
93 | if ((extent.getValue() % constant) != 0) |
94 | return std::pair<NonLoc, nonloc::ConcreteInt>(offset, extent); |
95 | else |
96 | return getSimplifiedOffsets( |
97 | nonloc::SymbolVal(SIE->getLHS()), |
98 | svalBuilder.makeIntVal(extent.getValue() / constant), |
99 | svalBuilder); |
100 | case BO_Add: |
101 | return getSimplifiedOffsets( |
102 | nonloc::SymbolVal(SIE->getLHS()), |
103 | svalBuilder.makeIntVal(extent.getValue() - constant), svalBuilder); |
104 | default: |
105 | break; |
106 | } |
107 | } |
108 | } |
109 | |
110 | return std::pair<NonLoc, nonloc::ConcreteInt>(offset, extent); |
111 | } |
112 | |
113 | void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, |
114 | const Stmt* LoadS, |
115 | CheckerContext &checkerContext) const { |
116 | |
117 | |
118 | |
119 | |
120 | |
121 | |
122 | |
123 | |
124 | |
125 | |
126 | ProgramStateRef state = checkerContext.getState(); |
127 | |
128 | SValBuilder &svalBuilder = checkerContext.getSValBuilder(); |
129 | const RegionRawOffsetV2 &rawOffset = |
130 | RegionRawOffsetV2::computeOffset(state, svalBuilder, location); |
131 | |
132 | if (!rawOffset.getRegion()) |
133 | return; |
134 | |
135 | NonLoc rawOffsetVal = rawOffset.getByteOffset(); |
136 | |
137 | |
138 | |
139 | |
140 | |
141 | SVal extentBegin = computeExtentBegin(svalBuilder, rawOffset.getRegion()); |
142 | |
143 | if (Optional<NonLoc> NV = extentBegin.getAs<NonLoc>()) { |
144 | if (NV->getAs<nonloc::ConcreteInt>()) { |
145 | std::pair<NonLoc, nonloc::ConcreteInt> simplifiedOffsets = |
146 | getSimplifiedOffsets(rawOffset.getByteOffset(), |
147 | NV->castAs<nonloc::ConcreteInt>(), |
148 | svalBuilder); |
149 | rawOffsetVal = simplifiedOffsets.first; |
150 | *NV = simplifiedOffsets.second; |
151 | } |
152 | |
153 | SVal lowerBound = svalBuilder.evalBinOpNN(state, BO_LT, rawOffsetVal, *NV, |
154 | svalBuilder.getConditionType()); |
155 | |
156 | Optional<NonLoc> lowerBoundToCheck = lowerBound.getAs<NonLoc>(); |
157 | if (!lowerBoundToCheck) |
158 | return; |
159 | |
160 | ProgramStateRef state_precedesLowerBound, state_withinLowerBound; |
161 | std::tie(state_precedesLowerBound, state_withinLowerBound) = |
162 | state->assume(*lowerBoundToCheck); |
163 | |
164 | |
165 | if (state_precedesLowerBound && !state_withinLowerBound) { |
166 | reportOOB(checkerContext, state_precedesLowerBound, OOB_Precedes); |
167 | return; |
168 | } |
169 | |
170 | |
171 | assert(state_withinLowerBound); |
172 | state = state_withinLowerBound; |
173 | } |
174 | |
175 | do { |
176 | |
177 | |
178 | DefinedOrUnknownSVal extentVal = |
179 | rawOffset.getRegion()->getExtent(svalBuilder); |
180 | if (!extentVal.getAs<NonLoc>()) |
181 | break; |
182 | |
183 | if (extentVal.getAs<nonloc::ConcreteInt>()) { |
184 | std::pair<NonLoc, nonloc::ConcreteInt> simplifiedOffsets = |
185 | getSimplifiedOffsets(rawOffset.getByteOffset(), |
186 | extentVal.castAs<nonloc::ConcreteInt>(), |
187 | svalBuilder); |
188 | rawOffsetVal = simplifiedOffsets.first; |
189 | extentVal = simplifiedOffsets.second; |
190 | } |
191 | |
192 | SVal upperbound = svalBuilder.evalBinOpNN(state, BO_GE, rawOffsetVal, |
193 | extentVal.castAs<NonLoc>(), |
194 | svalBuilder.getConditionType()); |
195 | |
196 | Optional<NonLoc> upperboundToCheck = upperbound.getAs<NonLoc>(); |
197 | if (!upperboundToCheck) |
198 | break; |
199 | |
200 | ProgramStateRef state_exceedsUpperBound, state_withinUpperBound; |
201 | std::tie(state_exceedsUpperBound, state_withinUpperBound) = |
202 | state->assume(*upperboundToCheck); |
203 | |
204 | |
205 | if (state_exceedsUpperBound && state_withinUpperBound) { |
206 | SVal ByteOffset = rawOffset.getByteOffset(); |
207 | if (state->isTainted(ByteOffset)) { |
208 | reportOOB(checkerContext, state_exceedsUpperBound, OOB_Tainted, |
209 | llvm::make_unique<TaintBugVisitor>(ByteOffset)); |
210 | return; |
211 | } |
212 | } else if (state_exceedsUpperBound) { |
213 | |
214 | |
215 | assert(!state_withinUpperBound); |
216 | reportOOB(checkerContext, state_exceedsUpperBound, OOB_Excedes); |
217 | return; |
218 | } |
219 | |
220 | assert(state_withinUpperBound); |
221 | state = state_withinUpperBound; |
222 | } |
223 | while (false); |
224 | |
225 | checkerContext.addTransition(state); |
226 | } |
227 | |
228 | void ArrayBoundCheckerV2::reportOOB( |
229 | CheckerContext &checkerContext, ProgramStateRef errorState, OOB_Kind kind, |
230 | std::unique_ptr<BugReporterVisitor> Visitor) const { |
231 | |
232 | ExplodedNode *errorNode = checkerContext.generateErrorNode(errorState); |
233 | if (!errorNode) |
234 | return; |
235 | |
236 | if (!BT) |
237 | BT.reset(new BuiltinBug(this, "Out-of-bound access")); |
238 | |
239 | |
240 | |
241 | |
242 | SmallString<256> buf; |
243 | llvm::raw_svector_ostream os(buf); |
244 | os << "Out of bound memory access "; |
245 | switch (kind) { |
246 | case OOB_Precedes: |
247 | os << "(accessed memory precedes memory block)"; |
248 | break; |
249 | case OOB_Excedes: |
250 | os << "(access exceeds upper limit of memory block)"; |
251 | break; |
252 | case OOB_Tainted: |
253 | os << "(index is tainted)"; |
254 | break; |
255 | } |
256 | |
257 | auto BR = llvm::make_unique<BugReport>(*BT, os.str(), errorNode); |
258 | BR->addVisitor(std::move(Visitor)); |
259 | checkerContext.emitReport(std::move(BR)); |
260 | } |
261 | |
262 | #ifndef NDEBUG |
263 | LLVM_DUMP_METHOD void RegionRawOffsetV2::dump() const { |
264 | dumpToStream(llvm::errs()); |
265 | } |
266 | |
267 | void RegionRawOffsetV2::dumpToStream(raw_ostream &os) const { |
268 | os << "raw_offset_v2{" << getRegion() << ',' << getByteOffset() << '}'; |
269 | } |
270 | #endif |
271 | |
272 | |
273 | |
274 | |
275 | static inline SVal getValue(SVal val, SValBuilder &svalBuilder) { |
276 | return val.getAs<UndefinedVal>() ? svalBuilder.makeArrayIndex(0) : val; |
277 | } |
278 | |
279 | |
280 | |
281 | static inline SVal scaleValue(ProgramStateRef state, |
282 | NonLoc baseVal, CharUnits scaling, |
283 | SValBuilder &sb) { |
284 | return sb.evalBinOpNN(state, BO_Mul, baseVal, |
285 | sb.makeArrayIndex(scaling.getQuantity()), |
286 | sb.getArrayIndexType()); |
287 | } |
288 | |
289 | |
290 | |
291 | static SVal addValue(ProgramStateRef state, SVal x, SVal y, |
292 | SValBuilder &svalBuilder) { |
293 | |
294 | |
295 | if (x.isUnknownOrUndef() || y.isUnknownOrUndef()) |
296 | return UnknownVal(); |
297 | |
298 | return svalBuilder.evalBinOpNN(state, BO_Add, x.castAs<NonLoc>(), |
299 | y.castAs<NonLoc>(), |
300 | svalBuilder.getArrayIndexType()); |
301 | } |
302 | |
303 | |
304 | |
305 | RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(ProgramStateRef state, |
306 | SValBuilder &svalBuilder, |
307 | SVal location) |
308 | { |
309 | const MemRegion *region = location.getAsRegion(); |
310 | SVal offset = UndefinedVal(); |
311 | |
312 | while (region) { |
313 | switch (region->getKind()) { |
314 | default: { |
315 | if (const SubRegion *subReg = dyn_cast<SubRegion>(region)) { |
316 | offset = getValue(offset, svalBuilder); |
317 | if (!offset.isUnknownOrUndef()) |
318 | return RegionRawOffsetV2(subReg, offset); |
319 | } |
320 | return RegionRawOffsetV2(); |
321 | } |
322 | case MemRegion::ElementRegionKind: { |
323 | const ElementRegion *elemReg = cast<ElementRegion>(region); |
324 | SVal index = elemReg->getIndex(); |
325 | if (!index.getAs<NonLoc>()) |
326 | return RegionRawOffsetV2(); |
327 | QualType elemType = elemReg->getElementType(); |
328 | |
329 | ASTContext &astContext = svalBuilder.getContext(); |
330 | if (elemType->isIncompleteType()) |
331 | return RegionRawOffsetV2(); |
332 | |
333 | |
334 | offset = addValue(state, |
335 | getValue(offset, svalBuilder), |
336 | scaleValue(state, |
337 | index.castAs<NonLoc>(), |
338 | astContext.getTypeSizeInChars(elemType), |
339 | svalBuilder), |
340 | svalBuilder); |
341 | |
342 | if (offset.isUnknownOrUndef()) |
343 | return RegionRawOffsetV2(); |
344 | |
345 | region = elemReg->getSuperRegion(); |
346 | continue; |
347 | } |
348 | } |
349 | } |
350 | return RegionRawOffsetV2(); |
351 | } |
352 | |
353 | void ento::registerArrayBoundCheckerV2(CheckerManager &mgr) { |
354 | mgr.registerChecker<ArrayBoundCheckerV2>(); |
355 | } |
356 | |
357 | bool ento::shouldRegisterArrayBoundCheckerV2(const LangOptions &LO) { |
358 | return true; |
359 | } |
360 | |