xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp (revision 26224ca2de193dcfd21ede7a846c28ea892b77e3)
1 //=== PointerSubChecker.cpp - Pointer subtraction checker ------*- 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 files defines PointerSubChecker, a builtin checker that checks for
10 // pointer subtractions on two pointers pointing to different memory chunks.
11 // This check corresponds to CWE-469.
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.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 "llvm/ADT/StringRef.h"
22 
23 using namespace clang;
24 using namespace ento;
25 
26 namespace {
27 class PointerSubChecker
28   : public Checker< check::PreStmt<BinaryOperator> > {
29   const BugType BT{this, "Pointer subtraction"};
30   const llvm::StringLiteral Msg_MemRegionDifferent =
31       "Subtraction of two pointers that do not point into the same array "
32       "is undefined behavior.";
33   const llvm::StringLiteral Msg_LargeArrayIndex =
34       "Using an array index greater than the array size at pointer subtraction "
35       "is undefined behavior.";
36   const llvm::StringLiteral Msg_NegativeArrayIndex =
37       "Using a negative array index at pointer subtraction "
38       "is undefined behavior.";
39   const llvm::StringLiteral Msg_BadVarIndex =
40       "Indexing the address of a variable with other than 1 at this place "
41       "is undefined behavior.";
42 
43   bool checkArrayBounds(CheckerContext &C, const Expr *E,
44                         const ElementRegion *ElemReg,
45                         const MemRegion *Reg) const;
46   void reportBug(CheckerContext &C, const Expr *E,
47                  const llvm::StringLiteral &Msg) const;
48 
49 public:
50   void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const;
51 };
52 }
53 
54 bool PointerSubChecker::checkArrayBounds(CheckerContext &C, const Expr *E,
55                                          const ElementRegion *ElemReg,
56                                          const MemRegion *Reg) const {
57   if (!ElemReg)
58     return true;
59 
60   ProgramStateRef State = C.getState();
61   const MemRegion *SuperReg = ElemReg->getSuperRegion();
62   SValBuilder &SVB = C.getSValBuilder();
63 
64   if (SuperReg == Reg) {
65     if (const llvm::APSInt *I = SVB.getKnownValue(State, ElemReg->getIndex());
66         I && (!I->isOne() && !I->isZero()))
67       reportBug(C, E, Msg_BadVarIndex);
68     return false;
69   }
70 
71   DefinedOrUnknownSVal ElemCount =
72       getDynamicElementCount(State, SuperReg, SVB, ElemReg->getElementType());
73   auto IndexTooLarge = SVB.evalBinOp(C.getState(), BO_GT, ElemReg->getIndex(),
74                                      ElemCount, SVB.getConditionType())
75                            .getAs<DefinedOrUnknownSVal>();
76   if (IndexTooLarge) {
77     ProgramStateRef S1, S2;
78     std::tie(S1, S2) = C.getState()->assume(*IndexTooLarge);
79     if (S1 && !S2) {
80       reportBug(C, E, Msg_LargeArrayIndex);
81       return false;
82     }
83   }
84   auto IndexTooSmall = SVB.evalBinOp(State, BO_LT, ElemReg->getIndex(),
85                                      SVB.makeZeroVal(SVB.getArrayIndexType()),
86                                      SVB.getConditionType())
87                            .getAs<DefinedOrUnknownSVal>();
88   if (IndexTooSmall) {
89     ProgramStateRef S1, S2;
90     std::tie(S1, S2) = State->assume(*IndexTooSmall);
91     if (S1 && !S2) {
92       reportBug(C, E, Msg_NegativeArrayIndex);
93       return false;
94     }
95   }
96   return true;
97 }
98 
99 void PointerSubChecker::reportBug(CheckerContext &C, const Expr *E,
100                                   const llvm::StringLiteral &Msg) const {
101   if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
102     auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
103     R->addRange(E->getSourceRange());
104     C.emitReport(std::move(R));
105   }
106 }
107 
108 void PointerSubChecker::checkPreStmt(const BinaryOperator *B,
109                                      CheckerContext &C) const {
110   // When doing pointer subtraction, if the two pointers do not point to the
111   // same array, emit a warning.
112   if (B->getOpcode() != BO_Sub)
113     return;
114 
115   SVal LV = C.getSVal(B->getLHS());
116   SVal RV = C.getSVal(B->getRHS());
117 
118   const MemRegion *LR = LV.getAsRegion();
119   const MemRegion *RR = RV.getAsRegion();
120   if (!LR || !RR)
121     return;
122 
123   // Allow subtraction of identical pointers.
124   if (LR == RR)
125     return;
126 
127   // No warning if one operand is unknown.
128   if (isa<SymbolicRegion>(LR) || isa<SymbolicRegion>(RR))
129     return;
130 
131   const auto *ElemLR = dyn_cast<ElementRegion>(LR);
132   const auto *ElemRR = dyn_cast<ElementRegion>(RR);
133 
134   if (!checkArrayBounds(C, B->getLHS(), ElemLR, RR))
135     return;
136   if (!checkArrayBounds(C, B->getRHS(), ElemRR, LR))
137     return;
138 
139   if (ElemLR && ElemRR) {
140     const MemRegion *SuperLR = ElemLR->getSuperRegion();
141     const MemRegion *SuperRR = ElemRR->getSuperRegion();
142     if (SuperLR == SuperRR)
143       return;
144     // Allow arithmetic on different symbolic regions.
145     if (isa<SymbolicRegion>(SuperLR) || isa<SymbolicRegion>(SuperRR))
146       return;
147   }
148 
149   reportBug(C, B, Msg_MemRegionDifferent);
150 }
151 
152 void ento::registerPointerSubChecker(CheckerManager &mgr) {
153   mgr.registerChecker<PointerSubChecker>();
154 }
155 
156 bool ento::shouldRegisterPointerSubChecker(const CheckerManager &mgr) {
157   return true;
158 }
159