| 1 | //== ArrayBoundChecker.cpp -------------------------------------------------==// |
| 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 | // This file defines security.ArrayBound, which is a path-sensitive checker |
| 10 | // that looks for out of bounds access of memory regions. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "clang/AST/CharUnits.h" |
| 15 | #include "clang/AST/ParentMapContext.h" |
| 16 | #include "clang/StaticAnalyzer/Checkers/BoundsChecking.h" |
| 17 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| 18 | #include "clang/StaticAnalyzer/Checkers/Taint.h" |
| 19 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
| 20 | #include "clang/StaticAnalyzer/Core/Checker.h" |
| 21 | #include "clang/StaticAnalyzer/Core/CheckerManager.h" |
| 22 | #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntType.h" |
| 23 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| 24 | #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" |
| 25 | #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" |
| 26 | #include "llvm/ADT/APSInt.h" |
| 27 | #include "llvm/Support/FormatVariadic.h" |
| 28 | #include "llvm/Support/raw_ostream.h" |
| 29 | #include <optional> |
| 30 | |
| 31 | using namespace clang; |
| 32 | using namespace ento; |
| 33 | using namespace taint; |
| 34 | using llvm::formatv; |
| 35 | |
| 36 | namespace { |
| 37 | /// If `E` is an array subscript expression with a base that is "clean" (= not |
| 38 | /// modified by pointer arithmetic = the beginning of a memory region), return |
| 39 | /// it as a pointer to ArraySubscriptExpr; otherwise return nullptr. |
| 40 | /// This helper function is used by two separate heuristics that are only valid |
| 41 | /// in these "clean" cases. |
| 42 | static const ArraySubscriptExpr * |
| 43 | getAsCleanArraySubscriptExpr(const Expr *E, const CheckerContext &C) { |
| 44 | const auto *ASE = dyn_cast<ArraySubscriptExpr>(Val: E); |
| 45 | if (!ASE) |
| 46 | return nullptr; |
| 47 | |
| 48 | const MemRegion *SubscriptBaseReg = C.getSVal(E: ASE->getBase()).getAsRegion(); |
| 49 | if (!SubscriptBaseReg) |
| 50 | return nullptr; |
| 51 | |
| 52 | // The base of the subscript expression is affected by pointer arithmetics, |
| 53 | // so we want to report byte offsets instead of indices and we don't want to |
| 54 | // activate the "index is unsigned -> cannot be negative" shortcut. |
| 55 | if (isa<ElementRegion>(Val: SubscriptBaseReg->StripCasts())) |
| 56 | return nullptr; |
| 57 | |
| 58 | return ASE; |
| 59 | } |
| 60 | |
| 61 | class SizeUnit { |
| 62 | QualType AsType; |
| 63 | int64_t AsCharUnits; |
| 64 | |
| 65 | SizeUnit() : AsType(), AsCharUnits(1) {} |
| 66 | |
| 67 | public: |
| 68 | SizeUnit(QualType T, const ASTContext &ACtx) |
| 69 | : AsType(T), AsCharUnits(ACtx.getTypeSizeInChars(T).getQuantity()) { |
| 70 | assert(!T.isNull()); |
| 71 | } |
| 72 | |
| 73 | static SizeUnit bytes() { return SizeUnit(); } |
| 74 | |
| 75 | bool isBytes() const { return AsType.isNull(); } |
| 76 | |
| 77 | /// Return the element type that is "natural" for reporting out-of-bounds |
| 78 | /// memory access to \p ER. |
| 79 | static SizeUnit forElementRegion(const ElementRegion *ER, |
| 80 | const ASTContext &ACtx) { |
| 81 | return SizeUnit(ER->getElementType(), ACtx); |
| 82 | } |
| 83 | |
| 84 | /// If `E` is a "clean" array subscript expression, return the type of the |
| 85 | /// accessed element; otherwise return 'Bytes' because that's the best (or |
| 86 | /// least bad) option for the assumption messages that use this. |
| 87 | /// FIXME: It is unfortunate that this heuristic differs from the heuristic |
| 88 | /// used for reporting assumption; but this difference is currently needed |
| 89 | /// due to the unfortunate phrasing of the assumption messages. |
| 90 | /// Get rid of this when the assumption note is rephrased and improved. |
| 91 | static SizeUnit forExpr(const Expr *E, const CheckerContext &C) { |
| 92 | const auto *ASE = getAsCleanArraySubscriptExpr(E, C); |
| 93 | if (!ASE) |
| 94 | return bytes(); |
| 95 | |
| 96 | return SizeUnit(ASE->getType(), C.getASTContext()); |
| 97 | } |
| 98 | |
| 99 | int64_t asCharUnits() const { return AsCharUnits; } |
| 100 | |
| 101 | bool canExpress(std::optional<int64_t> Val) const { |
| 102 | return asCharUnits() && (!Val || !(*Val % asCharUnits())); |
| 103 | } |
| 104 | |
| 105 | std::string asExtentDesc() const { |
| 106 | if (isBytes()) |
| 107 | return "the extent of" ; |
| 108 | return formatv(Fmt: "the number of '{0}' elements in" , Vals: AsType.getAsString()); |
| 109 | } |
| 110 | |
| 111 | std::string asElementName() const { |
| 112 | if (isBytes()) |
| 113 | return "byte" ; |
| 114 | return formatv(Fmt: "'{0}' element" , Vals: AsType.getAsString()); |
| 115 | } |
| 116 | }; |
| 117 | |
| 118 | /// Strings that will be passed to the parameters 'desc' and 'fullDesc' of the |
| 119 | /// constructor of 'PathSensitiveBugReport'. |
| 120 | struct BugDescription { |
| 121 | std::string Short; |
| 122 | std::string Full; |
| 123 | }; |
| 124 | |
| 125 | // NOTE: The `ArraySubscriptExpr` and `UnaryOperator` callbacks are `PostStmt` |
| 126 | // instead of `PreStmt` because the current implementation passes the whole |
| 127 | // expression to `CheckerContext::getSVal()` which only works after the |
| 128 | // symbolic evaluation of the expression. (To turn them into `PreStmt` |
| 129 | // callbacks, we'd need to duplicate the logic that evaluates these |
| 130 | // expressions.) The `MemberExpr` callback would work as `PreStmt` but it's |
| 131 | // defined as `PostStmt` for the sake of consistency with the other callbacks. |
| 132 | class ArrayBoundChecker : public Checker<check::PostStmt<ArraySubscriptExpr>, |
| 133 | check::PostStmt<UnaryOperator>, |
| 134 | check::PostStmt<MemberExpr>> { |
| 135 | BugType BT{this, "Out-of-bound access" }; |
| 136 | BugType TaintBT{this, "Out-of-bound access" , categories::TaintedData}; |
| 137 | |
| 138 | void handleAccessExpr(const Expr *E, CheckerContext &C) const; |
| 139 | |
| 140 | void reportOOB(CheckerContext &C, ProgramStateRef ErrorState, |
| 141 | BugDescription Desc, NonLoc Offset, |
| 142 | std::optional<NonLoc> Extent, bool IsTaintBug = false) const; |
| 143 | |
| 144 | static void markPartsInteresting(PathSensitiveBugReport &BR, |
| 145 | ProgramStateRef ErrorState, NonLoc Val, |
| 146 | bool MarkTaint); |
| 147 | |
| 148 | static bool isFromCtypeMacro(const Expr *E, ASTContext &AC); |
| 149 | |
| 150 | static bool isOffsetObviouslyNonnegative(const Expr *E, CheckerContext &C); |
| 151 | |
| 152 | static bool isInAddressOf(const Stmt *S, ASTContext &AC); |
| 153 | |
| 154 | public: |
| 155 | void checkPostStmt(const ArraySubscriptExpr *E, CheckerContext &C) const { |
| 156 | handleAccessExpr(E, C); |
| 157 | } |
| 158 | void checkPostStmt(const UnaryOperator *E, CheckerContext &C) const { |
| 159 | if (E->getOpcode() == UO_Deref) |
| 160 | handleAccessExpr(E, C); |
| 161 | } |
| 162 | void checkPostStmt(const MemberExpr *E, CheckerContext &C) const { |
| 163 | if (E->isArrow()) |
| 164 | handleAccessExpr(E: E->getBase(), C); |
| 165 | } |
| 166 | }; |
| 167 | |
| 168 | } // anonymous namespace |
| 169 | |
| 170 | /// Return true if information about the value of \p SV can put constraints |
| 171 | /// on some symbol which is interesting within the bug report \p BR. |
| 172 | /// In particular, this returns true when \p SV is interesting within \p BR; |
| 173 | /// but it also returns true if \p SV is an expression that contains integer |
| 174 | /// constants and a single symbolic operand which is interesting (in \p BR). |
| 175 | /// We need to use this instead of plain `BR.isInteresting()` because if we |
| 176 | /// are analyzing code like |
| 177 | /// int array[10]; |
| 178 | /// int f(int arg) { |
| 179 | /// return array[arg] && array[arg + 10]; |
| 180 | /// } |
| 181 | /// then the byte offsets are `arg * 4` and `(arg + 10) * 4`, which are not |
| 182 | /// sub-expressions of each other (but `getSimplifiedOffsets` is smart enough |
| 183 | /// to detect this out of bounds access). |
| 184 | static bool isDeterminedByInterestingSymbol(SVal SV, |
| 185 | PathSensitiveBugReport &BR) { |
| 186 | SymbolRef Sym = SV.getAsSymbol(); |
| 187 | if (!Sym) |
| 188 | return false; |
| 189 | for (SymbolRef PartSym : Sym->symbols()) { |
| 190 | // The interestingess mark may appear on any layer as we're stripping off |
| 191 | // the SymIntExpr, UnarySymExpr etc. layers... |
| 192 | if (BR.isInteresting(sym: PartSym)) |
| 193 | return true; |
| 194 | // ...but if both sides of the expression are symbolic, then there is no |
| 195 | // practical algorithm to produce separate constraints for the two |
| 196 | // operands (from the single combined result). |
| 197 | if (isa<SymSymExpr>(Val: PartSym)) |
| 198 | return false; |
| 199 | } |
| 200 | return false; |
| 201 | } |
| 202 | |
| 203 | static int64_t getElementSize(const ElementRegion *ER, SValBuilder &SVB) { |
| 204 | QualType ElemType = ER->getElementType(); |
| 205 | |
| 206 | assert(!ElemType->isIncompleteType() && "ElemType cannot be incomplete" ); |
| 207 | |
| 208 | return SVB.getContext().getTypeSizeInChars(T: ElemType).getQuantity(); |
| 209 | } |
| 210 | |
| 211 | /// For a given \p CurRegion that can be represented as a symbolic expression |
| 212 | /// Arr[Idx] (or perhaps Arr[Idx1][Idx2] etc.), return the parent memory block |
| 213 | /// Arr and the distance of Location from the beginning of Arr (expressed in a |
| 214 | /// NonLoc that specifies the number of CharUnits). Returns nullopt when these |
| 215 | /// cannot be determined. |
| 216 | static std::optional<std::pair<const SubRegion *, NonLoc>> |
| 217 | computeOffset(ProgramStateRef State, SValBuilder &SVB, |
| 218 | const ElementRegion *CurRegion) { |
| 219 | QualType T = SVB.getArrayIndexType(); |
| 220 | auto EvalBinOp = [&SVB, State, T](BinaryOperatorKind Op, NonLoc L, NonLoc R) { |
| 221 | // We will use this utility to add and multiply values. |
| 222 | return SVB.evalBinOpNN(state: State, op: Op, lhs: L, rhs: R, resultTy: T).getAs<NonLoc>(); |
| 223 | }; |
| 224 | |
| 225 | const SubRegion *OwnerRegion = nullptr; |
| 226 | std::optional<NonLoc> Offset = SVB.makeZeroArrayIndex(); |
| 227 | |
| 228 | while (CurRegion) { |
| 229 | const auto Index = CurRegion->getIndex().getAs<NonLoc>(); |
| 230 | if (!Index) |
| 231 | return std::nullopt; |
| 232 | |
| 233 | // Calculate Delta = Index * sizeof(ElemType). |
| 234 | NonLoc Size = SVB.makeArrayIndex(idx: getElementSize(ER: CurRegion, SVB)); |
| 235 | auto Delta = EvalBinOp(BO_Mul, *Index, Size); |
| 236 | if (!Delta) |
| 237 | return std::nullopt; |
| 238 | |
| 239 | // Perform Offset += Delta. |
| 240 | Offset = EvalBinOp(BO_Add, *Offset, *Delta); |
| 241 | if (!Offset) |
| 242 | return std::nullopt; |
| 243 | |
| 244 | OwnerRegion = CurRegion->getSuperRegion()->getAs<SubRegion>(); |
| 245 | // When this is just another ElementRegion layer, we need to continue the |
| 246 | // offset calculations: |
| 247 | CurRegion = dyn_cast_or_null<ElementRegion>(Val: OwnerRegion); |
| 248 | } |
| 249 | |
| 250 | if (OwnerRegion) |
| 251 | return std::make_pair(x&: OwnerRegion, y&: *Offset); |
| 252 | |
| 253 | return std::nullopt; |
| 254 | } |
| 255 | |
| 256 | static std::optional<int64_t> getConcreteValue(NonLoc SV) { |
| 257 | if (auto ConcreteVal = SV.getAs<nonloc::ConcreteInt>()) { |
| 258 | return ConcreteVal->getValue()->tryExtValue(); |
| 259 | } |
| 260 | return std::nullopt; |
| 261 | } |
| 262 | |
| 263 | static std::optional<int64_t> getConcreteValue(std::optional<NonLoc> SV) { |
| 264 | return SV ? getConcreteValue(SV: *SV) : std::nullopt; |
| 265 | } |
| 266 | |
| 267 | static StringRef getAdjective(const bounds::CheckResult &R) { |
| 268 | return (R.mayUnderflow() |
| 269 | ? (R.mayOverflow() ? "a negative or overflowing" : "a negative" ) |
| 270 | : (R.mayOverflow() ? "an overflowing" : "a valid" )); |
| 271 | } |
| 272 | |
| 273 | static StringRef getPreposition(const bounds::CheckResult &R) { |
| 274 | return (R.mayUnderflow() ? (R.mayOverflow() ? "around" : "preceding" ) |
| 275 | : (R.mayOverflow() ? "after the end of" : "within" )); |
| 276 | } |
| 277 | |
| 278 | static BugDescription describeInvalidAccess(bounds::CheckResult Res, |
| 279 | StringRef RegName, SizeUnit SU) { |
| 280 | assert(Res.mayBeInvalid()); |
| 281 | |
| 282 | std::optional<int64_t> OffsetN = getConcreteValue(SV: Res.getOffset()); |
| 283 | std::optional<int64_t> ExtentN = |
| 284 | getConcreteValue(SV: Res.getExtentIfMayOverflow()); |
| 285 | |
| 286 | if (SU.canExpress(Val: OffsetN) && SU.canExpress(Val: ExtentN)) { |
| 287 | if (OffsetN) |
| 288 | *OffsetN /= SU.asCharUnits(); |
| 289 | if (ExtentN) |
| 290 | *ExtentN /= SU.asCharUnits(); |
| 291 | } else { |
| 292 | // Fall back to reporting the offsets in bytes. |
| 293 | SU = SizeUnit::bytes(); |
| 294 | } |
| 295 | |
| 296 | StringRef OffsetOrIndex = SU.isBytes() ? "byte offset" : "index" ; |
| 297 | |
| 298 | SmallString<256> Buf; |
| 299 | llvm::raw_svector_ostream Out(Buf); |
| 300 | Out << "Access of " ; |
| 301 | if (OffsetN && !ExtentN && !SU.isBytes()) { |
| 302 | // If the offset is reported as an index, then the report must mention the |
| 303 | // element type (because it is not always clear from the code). It's more |
| 304 | // natural to mention the element type later where the extent is described, |
| 305 | // but if the extent is unknown/irrelevant, then the element type can be |
| 306 | // inserted into the message at this point. |
| 307 | Out << SU.asElementName() << " in " ; |
| 308 | } |
| 309 | Out << RegName << " at " ; |
| 310 | if (OffsetN) { |
| 311 | if (Res.mayUnderflow() && !Res.mayOverflow()) |
| 312 | Out << "negative " ; |
| 313 | Out << OffsetOrIndex << " " << *OffsetN; |
| 314 | } else { |
| 315 | Out << getAdjective(R: Res) << " " << OffsetOrIndex; |
| 316 | } |
| 317 | if (ExtentN) { |
| 318 | Out << ", while it holds only " ; |
| 319 | if (*ExtentN != 1) |
| 320 | Out << *ExtentN; |
| 321 | else |
| 322 | Out << "a single" ; |
| 323 | |
| 324 | Out << ' ' << SU.asElementName(); |
| 325 | |
| 326 | if (*ExtentN != 1) |
| 327 | Out << "s" ; |
| 328 | } |
| 329 | |
| 330 | return {.Short: formatv(Fmt: "Out of bound access to memory {0} {1}" , Vals: getPreposition(R: Res), |
| 331 | Vals&: RegName), |
| 332 | .Full: std::string(Buf)}; |
| 333 | } |
| 334 | |
| 335 | static BugDescription describeTaintBug(bounds::CheckResult Res, |
| 336 | StringRef RegName, |
| 337 | StringRef OffsetName) { |
| 338 | assert(Res.mayBeInvalid()); |
| 339 | return {.Short: formatv(Fmt: "Potential out of bound access to {0} with tainted {1}" , |
| 340 | Vals&: RegName, Vals&: OffsetName), |
| 341 | .Full: formatv(Fmt: "Access of {0} with a tainted {1} that may be{2}{3}{4}" , |
| 342 | Vals&: RegName, Vals&: OffsetName, Vals: Res.mayUnderflow() ? " negative" : "" , |
| 343 | Vals: (Res.mayUnderflow() && Res.mayOverflow()) ? " or" : "" , |
| 344 | Vals: Res.mayOverflow() ? " too large" : "" )}; |
| 345 | } |
| 346 | |
| 347 | /// When the access was ambiguous (that is, mayBeInBounds() && mayBeInvalid()), |
| 348 | /// returns the note "assuming in bounds" note that is relevant for the bug |
| 349 | /// report \p BR. When the access wasn't ambiguous or the the assumption is |
| 350 | /// irrelevant for \p BR, this returns the empty string (which signifies "do |
| 351 | /// not emit a note tag" when returned by a note tag callback). |
| 352 | static std::string getAssumptionNote(bounds::CheckResult Res, |
| 353 | PathSensitiveBugReport &BR, |
| 354 | StringRef RegName, SizeUnit SU) { |
| 355 | bool ShouldReportNonNegative = Res.mayUnderflow(); |
| 356 | if (!isDeterminedByInterestingSymbol(SV: Res.getOffset(), BR)) { |
| 357 | std::optional<NonLoc> E = Res.getExtentIfMayOverflow(); |
| 358 | if (E && isDeterminedByInterestingSymbol(SV: *E, BR)) { |
| 359 | // Even if the byte offset isn't interesting (e.g. it's a constant value), |
| 360 | // the assumption can still be interesting if it provides information |
| 361 | // about an interesting symbolic upper bound. |
| 362 | ShouldReportNonNegative = false; |
| 363 | } else { |
| 364 | // We don't have anything interesting, don't report the assumption. |
| 365 | return "" ; |
| 366 | } |
| 367 | } |
| 368 | |
| 369 | std::optional<int64_t> OffsetN = getConcreteValue(SV: Res.getOffset()); |
| 370 | std::optional<int64_t> ExtentN = |
| 371 | getConcreteValue(SV: Res.getExtentIfMayOverflow()); |
| 372 | |
| 373 | if (SU.canExpress(Val: OffsetN) && SU.canExpress(Val: ExtentN)) { |
| 374 | if (OffsetN) |
| 375 | *OffsetN /= SU.asCharUnits(); |
| 376 | if (ExtentN) |
| 377 | *ExtentN /= SU.asCharUnits(); |
| 378 | } else { |
| 379 | // Fall back to reporting the offsets in bytes. |
| 380 | SU = SizeUnit::bytes(); |
| 381 | } |
| 382 | |
| 383 | SmallString<256> Buf; |
| 384 | llvm::raw_svector_ostream Out(Buf); |
| 385 | Out << "Assuming " ; |
| 386 | if (!SU.isBytes()) { |
| 387 | Out << "index " ; |
| 388 | if (OffsetN) |
| 389 | Out << "'" << OffsetN << "' " ; |
| 390 | } else if (Res.mayOverflow()) { |
| 391 | Out << "byte offset " ; |
| 392 | if (OffsetN) |
| 393 | Out << "'" << OffsetN << "' " ; |
| 394 | } else { |
| 395 | Out << "offset " ; |
| 396 | } |
| 397 | |
| 398 | Out << "is" ; |
| 399 | if (ShouldReportNonNegative) { |
| 400 | Out << " non-negative" ; |
| 401 | } |
| 402 | if (Res.mayOverflow()) { |
| 403 | if (ShouldReportNonNegative) |
| 404 | Out << " and" ; |
| 405 | Out << " less than " ; |
| 406 | if (ExtentN) |
| 407 | Out << *ExtentN << ", " ; |
| 408 | Out << SU.asExtentDesc() << ' ' << RegName; |
| 409 | } |
| 410 | return std::string(Out.str()); |
| 411 | } |
| 412 | |
| 413 | void ArrayBoundChecker::handleAccessExpr(const Expr *E, |
| 414 | CheckerContext &C) const { |
| 415 | ASTContext &ACtx = C.getASTContext(); |
| 416 | const ElementRegion *AccessedER = |
| 417 | dyn_cast_or_null<ElementRegion>(Val: C.getSVal(E).getAsRegion()); |
| 418 | if (!AccessedER) |
| 419 | return; |
| 420 | |
| 421 | // The header ctype.h (from e.g. glibc) implements the isXXXXX() macros as |
| 422 | // #define isXXXXX(arg) (LOOKUP_TABLE[arg] & BITMASK_FOR_XXXXX) |
| 423 | // and incomplete analysis of these leads to false positives. As even |
| 424 | // accurate reports would be confusing for the users, just disable reports |
| 425 | // from these macros: |
| 426 | if (isFromCtypeMacro(E, AC&: ACtx)) |
| 427 | return; |
| 428 | |
| 429 | ProgramStateRef State = C.getState(); |
| 430 | SValBuilder &SVB = C.getSValBuilder(); |
| 431 | |
| 432 | const std::optional<std::pair<const SubRegion *, NonLoc>> &RawOffset = |
| 433 | computeOffset(State, SVB, CurRegion: AccessedER); |
| 434 | |
| 435 | if (!RawOffset) |
| 436 | return; |
| 437 | |
| 438 | auto [Reg, ByteOffset] = *RawOffset; |
| 439 | |
| 440 | const MemSpaceRegion *Space = Reg->getMemorySpace(State); |
| 441 | auto Extent = getDynamicExtent(State, MR: Reg, SVB).getAs<NonLoc>(); |
| 442 | |
| 443 | // A symbolic region in unknown space represents an unknown pointer that |
| 444 | // may point into the middle of an array, so we don't look for underflows. |
| 445 | // Both conditions are significant because we want to check underflows in |
| 446 | // symbolic regions on the heap (which may be introduced by checkers like |
| 447 | // MallocChecker that call SValBuilder::getConjuredHeapSymbolVal()) and |
| 448 | // non-symbolic regions (e.g. a field subregion of a symbolic region) in |
| 449 | // unknown space. |
| 450 | |
| 451 | bounds::CheckFlags Flags = { |
| 452 | /*CheckUnderflow=*/!(isa<SymbolicRegion>(Val: Reg) && |
| 453 | isa<UnknownSpaceRegion>(Val: Space)), |
| 454 | /*OffsetObviouslyNonnegative=*/isOffsetObviouslyNonnegative(E, C), |
| 455 | /*AlsoAcceptEquality=*/(getElementSize(ER: AccessedER, SVB) == 0)}; |
| 456 | |
| 457 | bounds::CheckResult Res = checkBounds(State, SVB, Offset: ByteOffset, Extent, Flags); |
| 458 | |
| 459 | if (Res.isCorruptedState()) { |
| 460 | C.addSink(); |
| 461 | return; |
| 462 | } |
| 463 | |
| 464 | std::string RegName = |
| 465 | Reg->getDescriptiveName(/*UseQuotes=*/true, /*AllowFallback=*/true); |
| 466 | |
| 467 | const NoteTag *T = nullptr; |
| 468 | if (Res.mayBeInvalid()) { |
| 469 | if (!Res.mayBeInBounds()) { |
| 470 | if (isa<ArraySubscriptExpr>(Val: E) && isInAddressOf(S: E, AC&: ACtx) && Extent) { |
| 471 | // Recognize and accept the idiomatic `&array[size]` expression that |
| 472 | // forms the past-the-end pointer without actually dereferencing it. |
| 473 | auto [EqualsToThreshold, NotEqualToThreshold] = |
| 474 | bounds::compareValueToThreshold(State, SVB, Value: ByteOffset, Threshold: *Extent, |
| 475 | CmpKind: bounds::Comparison::EQ); |
| 476 | if (EqualsToThreshold && !NotEqualToThreshold) { |
| 477 | C.addTransition(State: EqualsToThreshold); |
| 478 | return; |
| 479 | } |
| 480 | } |
| 481 | |
| 482 | SizeUnit SU = SizeUnit::forElementRegion(ER: AccessedER, ACtx); |
| 483 | BugDescription Desc = describeInvalidAccess(Res, RegName, SU); |
| 484 | reportOOB(C, ErrorState: State, Desc, Offset: ByteOffset, Extent: Res.getExtentIfMayOverflow()); |
| 485 | return; |
| 486 | } |
| 487 | |
| 488 | if (isTainted(State, V: ByteOffset)) { |
| 489 | // Diagnostic detail: saying "tainted offset" is always correct, but |
| 490 | // the common case is that 'idx' is tainted in 'arr[idx]' and then it's |
| 491 | // nicer to say "tainted index". |
| 492 | StringRef OffsetName = "offset" ; |
| 493 | if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(Val: E)) |
| 494 | if (isTainted(State, E: ASE->getIdx(), SF: C.getStackFrame())) |
| 495 | OffsetName = "index" ; |
| 496 | |
| 497 | BugDescription Desc = describeTaintBug(Res, RegName, OffsetName); |
| 498 | reportOOB(C, ErrorState: State, Desc, Offset: ByteOffset, Extent: Res.getExtentIfMayOverflow(), |
| 499 | /*IsTaintBug=*/true); |
| 500 | return; |
| 501 | } |
| 502 | |
| 503 | SizeUnit SU = SizeUnit::forExpr(E, C); |
| 504 | T = C.getNoteTag( |
| 505 | Cb: [Res, RegName, SU](PathSensitiveBugReport &BR) -> std::string { |
| 506 | return getAssumptionNote(Res, BR, RegName, SU); |
| 507 | }); |
| 508 | } |
| 509 | |
| 510 | C.addTransition(State: Res.getInBoundsState(), Tag: T); |
| 511 | } |
| 512 | |
| 513 | void ArrayBoundChecker::markPartsInteresting(PathSensitiveBugReport &BR, |
| 514 | ProgramStateRef ErrorState, |
| 515 | NonLoc Val, bool MarkTaint) { |
| 516 | if (SymbolRef Sym = Val.getAsSymbol()) { |
| 517 | // If the offset is a symbolic value, iterate over its "parts" with |
| 518 | // `SymExpr::symbols()` and mark each of them as interesting. |
| 519 | // For example, if the offset is `x*4 + y` then we put interestingness onto |
| 520 | // the SymSymExpr `x*4 + y`, the SymIntExpr `x*4` and the two data symbols |
| 521 | // `x` and `y`. |
| 522 | for (SymbolRef PartSym : Sym->symbols()) |
| 523 | BR.markInteresting(sym: PartSym); |
| 524 | } |
| 525 | |
| 526 | if (MarkTaint) { |
| 527 | // If the issue that we're reporting depends on the taintedness of the |
| 528 | // offset, then put interestingness onto symbols that could be the origin |
| 529 | // of the taint. Note that this may find symbols that did not appear in |
| 530 | // `Sym->symbols()` (because they're only loosely connected to `Val`). |
| 531 | for (SymbolRef Sym : getTaintedSymbols(State: ErrorState, V: Val)) |
| 532 | BR.markInteresting(sym: Sym); |
| 533 | } |
| 534 | } |
| 535 | |
| 536 | void ArrayBoundChecker::reportOOB(CheckerContext &C, ProgramStateRef ErrorState, |
| 537 | BugDescription Desc, NonLoc Offset, |
| 538 | std::optional<NonLoc> Extent, |
| 539 | bool IsTaintBug /*=false*/) const { |
| 540 | |
| 541 | ExplodedNode *ErrorNode = C.generateErrorNode(State: ErrorState); |
| 542 | if (!ErrorNode) |
| 543 | return; |
| 544 | |
| 545 | auto BR = std::make_unique<PathSensitiveBugReport>( |
| 546 | args: IsTaintBug ? TaintBT : BT, args&: Desc.Short, args&: Desc.Full, args&: ErrorNode); |
| 547 | |
| 548 | // FIXME: ideally we would just call trackExpressionValue() and that would |
| 549 | // "do the right thing": mark the relevant symbols as interesting, track the |
| 550 | // control dependencies and statements storing the relevant values and add |
| 551 | // helpful diagnostic pieces. However, right now trackExpressionValue() is |
| 552 | // a heap of unreliable heuristics, so it would cause several issues: |
| 553 | // - Interestingness is not applied consistently, e.g. if `array[x+10]` |
| 554 | // causes an overflow, then `x` is not marked as interesting. |
| 555 | // - We get irrelevant diagnostic pieces, e.g. in the code |
| 556 | // `int *p = (int*)malloc(2*sizeof(int)); p[3] = 0;` |
| 557 | // it places a "Storing uninitialized value" note on the `malloc` call |
| 558 | // (which is technically true, but irrelevant). |
| 559 | // If trackExpressionValue() becomes reliable, it should be applied instead |
| 560 | // of this custom markPartsInteresting(). |
| 561 | markPartsInteresting(BR&: *BR, ErrorState, Val: Offset, MarkTaint: IsTaintBug); |
| 562 | if (Extent) |
| 563 | markPartsInteresting(BR&: *BR, ErrorState, Val: *Extent, MarkTaint: IsTaintBug); |
| 564 | |
| 565 | C.emitReport(R: std::move(BR)); |
| 566 | } |
| 567 | |
| 568 | bool ArrayBoundChecker::isFromCtypeMacro(const Expr *E, ASTContext &ACtx) { |
| 569 | SourceLocation Loc = E->getBeginLoc(); |
| 570 | if (!Loc.isMacroID()) |
| 571 | return false; |
| 572 | |
| 573 | StringRef MacroName = Lexer::getImmediateMacroName( |
| 574 | Loc, SM: ACtx.getSourceManager(), LangOpts: ACtx.getLangOpts()); |
| 575 | |
| 576 | if (MacroName.size() < 7 || MacroName[0] != 'i' || MacroName[1] != 's') |
| 577 | return false; |
| 578 | |
| 579 | return ((MacroName == "isalnum" ) || (MacroName == "isalpha" ) || |
| 580 | (MacroName == "isblank" ) || (MacroName == "isdigit" ) || |
| 581 | (MacroName == "isgraph" ) || (MacroName == "islower" ) || |
| 582 | (MacroName == "isnctrl" ) || (MacroName == "isprint" ) || |
| 583 | (MacroName == "ispunct" ) || (MacroName == "isspace" ) || |
| 584 | (MacroName == "isupper" ) || (MacroName == "isxdigit" )); |
| 585 | } |
| 586 | |
| 587 | bool ArrayBoundChecker::isOffsetObviouslyNonnegative(const Expr *E, |
| 588 | CheckerContext &C) { |
| 589 | const ArraySubscriptExpr *ASE = getAsCleanArraySubscriptExpr(E, C); |
| 590 | if (!ASE) |
| 591 | return false; |
| 592 | return ASE->getIdx()->getType()->isUnsignedIntegerOrEnumerationType(); |
| 593 | } |
| 594 | |
| 595 | bool ArrayBoundChecker::isInAddressOf(const Stmt *S, ASTContext &ACtx) { |
| 596 | ParentMapContext &ParentCtx = ACtx.getParentMapContext(); |
| 597 | do { |
| 598 | const DynTypedNodeList Parents = ParentCtx.getParents(Node: *S); |
| 599 | if (Parents.empty()) |
| 600 | return false; |
| 601 | S = Parents[0].get<Stmt>(); |
| 602 | } while (isa_and_nonnull<ParenExpr, ImplicitCastExpr>(Val: S)); |
| 603 | const auto *UnaryOp = dyn_cast_or_null<UnaryOperator>(Val: S); |
| 604 | return UnaryOp && UnaryOp->getOpcode() == UO_AddrOf; |
| 605 | } |
| 606 | |
| 607 | void ento::registerArrayBoundChecker(CheckerManager &mgr) { |
| 608 | mgr.registerChecker<ArrayBoundChecker>(); |
| 609 | } |
| 610 | |
| 611 | bool ento::shouldRegisterArrayBoundChecker(const CheckerManager &mgr) { |
| 612 | return true; |
| 613 | } |
| 614 | |