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