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