369 lines · cpp
1//=== PointerArithChecker.cpp - Pointer arithmetic 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-exception6//7//===----------------------------------------------------------------------===//8//9// This files defines PointerArithChecker, a builtin checker that checks for10// pointer arithmetic on locations other than array elements.11//12//===----------------------------------------------------------------------===//13 14#include "clang/AST/DeclCXX.h"15#include "clang/AST/ExprCXX.h"16#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"17#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"18#include "clang/StaticAnalyzer/Core/Checker.h"19#include "clang/StaticAnalyzer/Core/CheckerManager.h"20#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"21#include "llvm/ADT/StringRef.h"22 23using namespace clang;24using namespace ento;25 26namespace {27enum class AllocKind {28 SingleObject,29 Array,30 Unknown,31 Reinterpreted // Single object interpreted as an array.32};33} // end namespace34 35namespace llvm {36template <> struct FoldingSetTrait<AllocKind> {37 static inline void Profile(AllocKind X, FoldingSetNodeID &ID) {38 ID.AddInteger(static_cast<int>(X));39 }40};41} // end namespace llvm42 43namespace {44class PointerArithChecker45 : public Checker<46 check::PreStmt<BinaryOperator>, check::PreStmt<UnaryOperator>,47 check::PreStmt<ArraySubscriptExpr>, check::PreStmt<CastExpr>,48 check::PostStmt<CastExpr>, check::PostStmt<CXXNewExpr>,49 check::PostStmt<CallExpr>, check::DeadSymbols> {50 AllocKind getKindOfNewOp(const CXXNewExpr *NE, const FunctionDecl *FD) const;51 const MemRegion *getArrayRegion(const MemRegion *Region, bool &Polymorphic,52 AllocKind &AKind, CheckerContext &C) const;53 const MemRegion *getPointedRegion(const MemRegion *Region,54 CheckerContext &C) const;55 void reportPointerArithMisuse(const Expr *E, CheckerContext &C,56 bool PointedNeeded = false) const;57 void initAllocIdentifiers(ASTContext &C) const;58 59 const BugType BT_pointerArith{this, "Dangerous pointer arithmetic"};60 const BugType BT_polyArray{this, "Dangerous pointer arithmetic"};61 mutable llvm::SmallPtrSet<IdentifierInfo *, 8> AllocFunctions;62 63public:64 void checkPreStmt(const UnaryOperator *UOp, CheckerContext &C) const;65 void checkPreStmt(const BinaryOperator *BOp, CheckerContext &C) const;66 void checkPreStmt(const ArraySubscriptExpr *SubExpr, CheckerContext &C) const;67 void checkPreStmt(const CastExpr *CE, CheckerContext &C) const;68 void checkPostStmt(const CastExpr *CE, CheckerContext &C) const;69 void checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const;70 void checkPostStmt(const CallExpr *CE, CheckerContext &C) const;71 void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;72};73} // end namespace74 75REGISTER_MAP_WITH_PROGRAMSTATE(RegionState, const MemRegion *, AllocKind)76 77static bool isArrayPlacementNew(const CXXNewExpr *NE) {78 return NE->isArray() && NE->getNumPlacementArgs() > 0;79}80 81static ProgramStateRef markSuperRegionReinterpreted(ProgramStateRef State,82 const MemRegion *Region) {83 while (const auto *BaseRegion = dyn_cast<CXXBaseObjectRegion>(Region)) {84 Region = BaseRegion->getSuperRegion();85 }86 if (const auto *ElemRegion = dyn_cast<ElementRegion>(Region)) {87 State = State->set<RegionState>(ElemRegion->getSuperRegion(),88 AllocKind::Reinterpreted);89 }90 return State;91}92 93void PointerArithChecker::checkDeadSymbols(SymbolReaper &SR,94 CheckerContext &C) const {95 // TODO: intentional leak. Some information is garbage collected too early,96 // see http://reviews.llvm.org/D14203 for further information.97 /*ProgramStateRef State = C.getState();98 RegionStateTy RegionStates = State->get<RegionState>();99 for (const MemRegion *Reg: llvm::make_first_range(RegionStates)) {100 if (!SR.isLiveRegion(Reg))101 State = State->remove<RegionState>(Reg);102 }103 C.addTransition(State);*/104}105 106AllocKind PointerArithChecker::getKindOfNewOp(const CXXNewExpr *NE,107 const FunctionDecl *FD) const {108 // This checker try not to assume anything about placement and overloaded109 // new to avoid false positives.110 if (isa<CXXMethodDecl>(FD))111 return AllocKind::Unknown;112 if (FD->getNumParams() != 1 || FD->isVariadic())113 return AllocKind::Unknown;114 if (NE->isArray())115 return AllocKind::Array;116 117 return AllocKind::SingleObject;118}119 120const MemRegion *121PointerArithChecker::getPointedRegion(const MemRegion *Region,122 CheckerContext &C) const {123 assert(Region);124 ProgramStateRef State = C.getState();125 SVal S = State->getSVal(Region);126 return S.getAsRegion();127}128 129/// Checks whether a region is the part of an array.130/// In case there is a derived to base cast above the array element, the131/// Polymorphic output value is set to true. AKind output value is set to the132/// allocation kind of the inspected region.133const MemRegion *PointerArithChecker::getArrayRegion(const MemRegion *Region,134 bool &Polymorphic,135 AllocKind &AKind,136 CheckerContext &C) const {137 assert(Region);138 while (const auto *BaseRegion = dyn_cast<CXXBaseObjectRegion>(Region)) {139 Region = BaseRegion->getSuperRegion();140 Polymorphic = true;141 }142 if (const auto *ElemRegion = dyn_cast<ElementRegion>(Region)) {143 Region = ElemRegion->getSuperRegion();144 }145 146 ProgramStateRef State = C.getState();147 if (const AllocKind *Kind = State->get<RegionState>(Region)) {148 AKind = *Kind;149 if (*Kind == AllocKind::Array)150 return Region;151 else152 return nullptr;153 }154 // When the region is symbolic and we do not have any information about it,155 // assume that this is an array to avoid false positives.156 if (isa<SymbolicRegion>(Region))157 return Region;158 159 // No AllocKind stored and not symbolic, assume that it points to a single160 // object.161 return nullptr;162}163 164void PointerArithChecker::reportPointerArithMisuse(const Expr *E,165 CheckerContext &C,166 bool PointedNeeded) const {167 SourceRange SR = E->getSourceRange();168 if (SR.isInvalid())169 return;170 171 ProgramStateRef State = C.getState();172 const MemRegion *Region = C.getSVal(E).getAsRegion();173 if (!Region)174 return;175 if (PointedNeeded)176 Region = getPointedRegion(Region, C);177 if (!Region)178 return;179 180 bool IsPolymorphic = false;181 AllocKind Kind = AllocKind::Unknown;182 if (const MemRegion *ArrayRegion =183 getArrayRegion(Region, IsPolymorphic, Kind, C)) {184 if (!IsPolymorphic)185 return;186 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {187 constexpr llvm::StringLiteral Msg =188 "Pointer arithmetic on a pointer to base class is dangerous "189 "because derived and base class may have different size.";190 auto R = std::make_unique<PathSensitiveBugReport>(BT_polyArray, Msg, N);191 R->addRange(E->getSourceRange());192 R->markInteresting(ArrayRegion);193 C.emitReport(std::move(R));194 }195 return;196 }197 198 if (Kind == AllocKind::Reinterpreted)199 return;200 201 // We might not have enough information about symbolic regions.202 if (Kind != AllocKind::SingleObject &&203 Region->getKind() == MemRegion::Kind::SymbolicRegionKind)204 return;205 206 if (ExplodedNode *N = C.generateNonFatalErrorNode()) {207 constexpr llvm::StringLiteral Msg =208 "Pointer arithmetic on non-array variables relies on memory layout, "209 "which is dangerous.";210 auto R = std::make_unique<PathSensitiveBugReport>(BT_pointerArith, Msg, N);211 R->addRange(SR);212 R->markInteresting(Region);213 C.emitReport(std::move(R));214 }215}216 217void PointerArithChecker::initAllocIdentifiers(ASTContext &C) const {218 if (!AllocFunctions.empty())219 return;220 AllocFunctions.insert(&C.Idents.get("alloca"));221 AllocFunctions.insert(&C.Idents.get("malloc"));222 AllocFunctions.insert(&C.Idents.get("realloc"));223 AllocFunctions.insert(&C.Idents.get("calloc"));224 AllocFunctions.insert(&C.Idents.get("valloc"));225}226 227void PointerArithChecker::checkPostStmt(const CallExpr *CE,228 CheckerContext &C) const {229 ProgramStateRef State = C.getState();230 const FunctionDecl *FD = C.getCalleeDecl(CE);231 if (!FD)232 return;233 IdentifierInfo *FunI = FD->getIdentifier();234 initAllocIdentifiers(C.getASTContext());235 if (AllocFunctions.count(FunI) == 0)236 return;237 238 SVal SV = C.getSVal(CE);239 const MemRegion *Region = SV.getAsRegion();240 if (!Region)241 return;242 // Assume that C allocation functions allocate arrays to avoid false243 // positives.244 // TODO: Add heuristics to distinguish alloc calls that allocates single245 // objecs.246 State = State->set<RegionState>(Region, AllocKind::Array);247 C.addTransition(State);248}249 250void PointerArithChecker::checkPostStmt(const CXXNewExpr *NE,251 CheckerContext &C) const {252 const FunctionDecl *FD = NE->getOperatorNew();253 if (!FD)254 return;255 256 AllocKind Kind = getKindOfNewOp(NE, FD);257 258 ProgramStateRef State = C.getState();259 SVal AllocedVal = C.getSVal(NE);260 const MemRegion *Region = AllocedVal.getAsRegion();261 if (!Region)262 return;263 264 // For array placement-new, mark the original region as reinterpreted265 if (isArrayPlacementNew(NE)) {266 State = markSuperRegionReinterpreted(State, Region);267 }268 269 State = State->set<RegionState>(Region, Kind);270 C.addTransition(State);271}272 273void PointerArithChecker::checkPostStmt(const CastExpr *CE,274 CheckerContext &C) const {275 // Casts to `void*` happen, for instance, on placement new calls.276 // We consider `void*` not to erase the type information about the underlying277 // region.278 if (CE->getCastKind() != CastKind::CK_BitCast ||279 CE->getType()->isVoidPointerType())280 return;281 282 const Expr *CastedExpr = CE->getSubExpr();283 ProgramStateRef State = C.getState();284 SVal CastedVal = C.getSVal(CastedExpr);285 286 const MemRegion *Region = CastedVal.getAsRegion();287 if (!Region)288 return;289 290 // Suppress reinterpret casted hits.291 State = State->set<RegionState>(Region, AllocKind::Reinterpreted);292 C.addTransition(State);293}294 295void PointerArithChecker::checkPreStmt(const CastExpr *CE,296 CheckerContext &C) const {297 if (CE->getCastKind() != CastKind::CK_ArrayToPointerDecay)298 return;299 300 const Expr *CastedExpr = CE->getSubExpr();301 ProgramStateRef State = C.getState();302 SVal CastedVal = C.getSVal(CastedExpr);303 304 const MemRegion *Region = CastedVal.getAsRegion();305 if (!Region)306 return;307 308 if (const AllocKind *Kind = State->get<RegionState>(Region)) {309 if (*Kind == AllocKind::Array || *Kind == AllocKind::Reinterpreted)310 return;311 }312 State = State->set<RegionState>(Region, AllocKind::Array);313 C.addTransition(State);314}315 316void PointerArithChecker::checkPreStmt(const UnaryOperator *UOp,317 CheckerContext &C) const {318 if (!UOp->isIncrementDecrementOp() || !UOp->getType()->isPointerType())319 return;320 reportPointerArithMisuse(UOp->getSubExpr(), C, true);321}322 323void PointerArithChecker::checkPreStmt(const ArraySubscriptExpr *SubsExpr,324 CheckerContext &C) const {325 SVal Idx = C.getSVal(SubsExpr->getIdx());326 327 // Indexing with 0 is OK.328 if (Idx.isZeroConstant())329 return;330 331 // Indexing vector-type expressions is also OK.332 if (SubsExpr->getBase()->getType()->isVectorType())333 return;334 reportPointerArithMisuse(SubsExpr->getBase(), C);335}336 337void PointerArithChecker::checkPreStmt(const BinaryOperator *BOp,338 CheckerContext &C) const {339 BinaryOperatorKind OpKind = BOp->getOpcode();340 if (!BOp->isAdditiveOp() && OpKind != BO_AddAssign && OpKind != BO_SubAssign)341 return;342 343 const Expr *Lhs = BOp->getLHS();344 const Expr *Rhs = BOp->getRHS();345 ProgramStateRef State = C.getState();346 347 if (Rhs->getType()->isIntegerType() && Lhs->getType()->isPointerType()) {348 SVal RHSVal = C.getSVal(Rhs);349 if (State->isNull(RHSVal).isConstrainedTrue())350 return;351 reportPointerArithMisuse(Lhs, C, !BOp->isAdditiveOp());352 }353 // The int += ptr; case is not valid C++.354 if (Lhs->getType()->isIntegerType() && Rhs->getType()->isPointerType()) {355 SVal LHSVal = C.getSVal(Lhs);356 if (State->isNull(LHSVal).isConstrainedTrue())357 return;358 reportPointerArithMisuse(Rhs, C);359 }360}361 362void ento::registerPointerArithChecker(CheckerManager &mgr) {363 mgr.registerChecker<PointerArithChecker>();364}365 366bool ento::shouldRegisterPointerArithChecker(const CheckerManager &mgr) {367 return true;368}369