| 1 | //== ReturnPointerRangeChecker.cpp ------------------------------*- 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 | // This file defines ReturnPointerRangeChecker, which is a path-sensitive check |
| 10 | // which looks for an out-of-bound pointer being returned to callers. |
| 11 | // |
| 12 | //===----------------------------------------------------------------------===// |
| 13 | |
| 14 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| 15 | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.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/CheckerContext.h" |
| 20 | #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h" |
| 21 | #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" |
| 22 | |
| 23 | using namespace clang; |
| 24 | using namespace ento; |
| 25 | |
| 26 | namespace { |
| 27 | class ReturnPointerRangeChecker : |
| 28 | public Checker< check::PreStmt<ReturnStmt> > { |
| 29 | // FIXME: This bug correspond to CWE-466. Eventually we should have bug |
| 30 | // types explicitly reference such exploit categories (when applicable). |
| 31 | const BugType BT{this, "Buffer overflow" }; |
| 32 | |
| 33 | public: |
| 34 | void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const; |
| 35 | }; |
| 36 | } |
| 37 | |
| 38 | void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS, |
| 39 | CheckerContext &C) const { |
| 40 | ProgramStateRef state = C.getState(); |
| 41 | |
| 42 | const Expr *RetE = RS->getRetValue(); |
| 43 | if (!RetE) |
| 44 | return; |
| 45 | |
| 46 | // Skip "body farmed" functions. |
| 47 | if (RetE->getSourceRange().isInvalid()) |
| 48 | return; |
| 49 | |
| 50 | SVal V = C.getSVal(S: RetE); |
| 51 | const MemRegion *R = V.getAsRegion(); |
| 52 | |
| 53 | const ElementRegion *ER = dyn_cast_or_null<ElementRegion>(Val: R); |
| 54 | if (!ER) |
| 55 | return; |
| 56 | |
| 57 | DefinedOrUnknownSVal Idx = ER->getIndex().castAs<DefinedOrUnknownSVal>(); |
| 58 | // Zero index is always in bound, this also passes ElementRegions created for |
| 59 | // pointer casts. |
| 60 | if (Idx.isZeroConstant()) |
| 61 | return; |
| 62 | |
| 63 | // FIXME: All of this out-of-bounds checking should eventually be refactored |
| 64 | // into a common place. |
| 65 | DefinedOrUnknownSVal ElementCount = getDynamicElementCount( |
| 66 | State: state, MR: ER->getSuperRegion(), SVB&: C.getSValBuilder(), Ty: ER->getValueType()); |
| 67 | |
| 68 | // We assume that the location after the last element in the array is used as |
| 69 | // end() iterator. Reporting on these would return too many false positives. |
| 70 | if (Idx == ElementCount) |
| 71 | return; |
| 72 | |
| 73 | ProgramStateRef StInBound, StOutBound; |
| 74 | std::tie(args&: StInBound, args&: StOutBound) = state->assumeInBoundDual(idx: Idx, upperBound: ElementCount); |
| 75 | if (StOutBound && !StInBound) { |
| 76 | ExplodedNode *N = C.generateErrorNode(State: StOutBound); |
| 77 | |
| 78 | if (!N) |
| 79 | return; |
| 80 | |
| 81 | constexpr llvm::StringLiteral Msg = |
| 82 | "Returned pointer value points outside the original object " |
| 83 | "(potential buffer overflow)" ; |
| 84 | |
| 85 | // Generate a report for this bug. |
| 86 | auto Report = std::make_unique<PathSensitiveBugReport>(args: BT, args: Msg, args&: N); |
| 87 | Report->addRange(R: RetE->getSourceRange()); |
| 88 | |
| 89 | const auto ConcreteElementCount = ElementCount.getAs<nonloc::ConcreteInt>(); |
| 90 | const auto ConcreteIdx = Idx.getAs<nonloc::ConcreteInt>(); |
| 91 | |
| 92 | const auto *DeclR = ER->getSuperRegion()->getAs<DeclRegion>(); |
| 93 | |
| 94 | if (DeclR) |
| 95 | Report->addNote(Msg: "Original object declared here" , |
| 96 | Pos: {DeclR->getDecl(), C.getSourceManager()}); |
| 97 | |
| 98 | if (ConcreteElementCount) { |
| 99 | SmallString<128> SBuf; |
| 100 | llvm::raw_svector_ostream OS(SBuf); |
| 101 | OS << "Original object " ; |
| 102 | if (DeclR) { |
| 103 | OS << "'" ; |
| 104 | DeclR->getDecl()->printName(OS); |
| 105 | OS << "' " ; |
| 106 | } |
| 107 | OS << "is an array of " << ConcreteElementCount->getValue() << " '" ; |
| 108 | ER->getValueType().print(OS, |
| 109 | Policy: PrintingPolicy(C.getASTContext().getLangOpts())); |
| 110 | OS << "' objects" ; |
| 111 | if (ConcreteIdx) { |
| 112 | OS << ", returned pointer points at index " << ConcreteIdx->getValue(); |
| 113 | } |
| 114 | |
| 115 | Report->addNote(Msg: SBuf, |
| 116 | Pos: {RetE, C.getSourceManager(), C.getLocationContext()}); |
| 117 | } |
| 118 | |
| 119 | bugreporter::trackExpressionValue(N, E: RetE, R&: *Report); |
| 120 | |
| 121 | C.emitReport(R: std::move(Report)); |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | void ento::registerReturnPointerRangeChecker(CheckerManager &mgr) { |
| 126 | mgr.registerChecker<ReturnPointerRangeChecker>(); |
| 127 | } |
| 128 | |
| 129 | bool ento::shouldRegisterReturnPointerRangeChecker(const CheckerManager &mgr) { |
| 130 | return true; |
| 131 | } |
| 132 | |