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