1 //=== InnerPointerChecker.cpp -------------------------------------*- C++ -*--// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 // 10 // This file defines a check that marks a raw pointer to a C++ container's 11 // inner buffer released when the object is destroyed. This information can 12 // be used by MallocChecker to detect use-after-free problems. 13 // 14 //===----------------------------------------------------------------------===// 15 16 #include "AllocationState.h" 17 #include "ClangSACheckers.h" 18 #include "InterCheckerAPI.h" 19 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 20 #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" 21 #include "clang/StaticAnalyzer/Core/Checker.h" 22 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 23 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 24 25 using namespace clang; 26 using namespace ento; 27 28 using PtrSet = llvm::ImmutableSet<SymbolRef>; 29 30 // Associate container objects with a set of raw pointer symbols. 31 REGISTER_MAP_WITH_PROGRAMSTATE(RawPtrMap, const MemRegion *, PtrSet) 32 33 // This is a trick to gain access to PtrSet's Factory. 34 namespace clang { 35 namespace ento { 36 template <> 37 struct ProgramStateTrait<PtrSet> : public ProgramStatePartialTrait<PtrSet> { 38 static void *GDMIndex() { 39 static int Index = 0; 40 return &Index; 41 } 42 }; 43 } // end namespace ento 44 } // end namespace clang 45 46 namespace { 47 48 class InnerPointerChecker 49 : public Checker<check::DeadSymbols, check::PostCall> { 50 51 CallDescription AppendFn, AssignFn, ClearFn, CStrFn, DataFn, EraseFn, 52 InsertFn, PopBackFn, PushBackFn, ReplaceFn, ReserveFn, ResizeFn, 53 ShrinkToFitFn, SwapFn; 54 55 public: 56 class InnerPointerBRVisitor : public BugReporterVisitor { 57 SymbolRef PtrToBuf; 58 59 public: 60 InnerPointerBRVisitor(SymbolRef Sym) : PtrToBuf(Sym) {} 61 62 static void *getTag() { 63 static int Tag = 0; 64 return &Tag; 65 } 66 67 void Profile(llvm::FoldingSetNodeID &ID) const override { 68 ID.AddPointer(getTag()); 69 } 70 71 std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, 72 const ExplodedNode *PrevN, 73 BugReporterContext &BRC, 74 BugReport &BR) override; 75 76 // FIXME: Scan the map once in the visitor's constructor and do a direct 77 // lookup by region. 78 bool isSymbolTracked(ProgramStateRef State, SymbolRef Sym) { 79 RawPtrMapTy Map = State->get<RawPtrMap>(); 80 for (const auto Entry : Map) { 81 if (Entry.second.contains(Sym)) 82 return true; 83 } 84 return false; 85 } 86 }; 87 88 InnerPointerChecker() 89 : AppendFn("append"), AssignFn("assign"), ClearFn("clear"), 90 CStrFn("c_str"), DataFn("data"), EraseFn("erase"), InsertFn("insert"), 91 PopBackFn("pop_back"), PushBackFn("push_back"), ReplaceFn("replace"), 92 ReserveFn("reserve"), ResizeFn("resize"), 93 ShrinkToFitFn("shrink_to_fit"), SwapFn("swap") {} 94 95 /// Check if the object of this member function call is a `basic_string`. 96 bool isCalledOnStringObject(const CXXInstanceCall *ICall) const; 97 98 /// Check whether the called member function potentially invalidates 99 /// pointers referring to the container object's inner buffer. 100 bool isInvalidatingMemberFunction(const CallEvent &Call) const; 101 102 /// Mark pointer symbols associated with the given memory region released 103 /// in the program state. 104 void markPtrSymbolsReleased(const CallEvent &Call, ProgramStateRef State, 105 const MemRegion *ObjRegion, 106 CheckerContext &C) const; 107 108 /// Standard library functions that take a non-const `basic_string` argument by 109 /// reference may invalidate its inner pointers. Check for these cases and 110 /// mark the pointers released. 111 void checkFunctionArguments(const CallEvent &Call, ProgramStateRef State, 112 CheckerContext &C) const; 113 114 /// Record the connection between raw pointers referring to a container 115 /// object's inner buffer and the object's memory region in the program state. 116 /// Mark potentially invalidated pointers released. 117 void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 118 119 /// Clean up the program state map. 120 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 121 }; 122 123 } // end anonymous namespace 124 125 bool InnerPointerChecker::isCalledOnStringObject( 126 const CXXInstanceCall *ICall) const { 127 const auto *ObjRegion = 128 dyn_cast_or_null<TypedValueRegion>(ICall->getCXXThisVal().getAsRegion()); 129 if (!ObjRegion) 130 return false; 131 132 QualType ObjTy = ObjRegion->getValueType(); 133 if (ObjTy.isNull()) 134 return false; 135 136 CXXRecordDecl *Decl = ObjTy->getAsCXXRecordDecl(); 137 return Decl && Decl->getName() == "basic_string"; 138 } 139 140 bool InnerPointerChecker::isInvalidatingMemberFunction( 141 const CallEvent &Call) const { 142 if (const auto *MemOpCall = dyn_cast<CXXMemberOperatorCall>(&Call)) { 143 OverloadedOperatorKind Opc = MemOpCall->getOriginExpr()->getOperator(); 144 if (Opc == OO_Equal || Opc == OO_PlusEqual) 145 return true; 146 return false; 147 } 148 return (isa<CXXDestructorCall>(Call) || Call.isCalled(AppendFn) || 149 Call.isCalled(AssignFn) || Call.isCalled(ClearFn) || 150 Call.isCalled(EraseFn) || Call.isCalled(InsertFn) || 151 Call.isCalled(PopBackFn) || Call.isCalled(PushBackFn) || 152 Call.isCalled(ReplaceFn) || Call.isCalled(ReserveFn) || 153 Call.isCalled(ResizeFn) || Call.isCalled(ShrinkToFitFn) || 154 Call.isCalled(SwapFn)); 155 } 156 157 void InnerPointerChecker::markPtrSymbolsReleased(const CallEvent &Call, 158 ProgramStateRef State, 159 const MemRegion *MR, 160 CheckerContext &C) const { 161 if (const PtrSet *PS = State->get<RawPtrMap>(MR)) { 162 const Expr *Origin = Call.getOriginExpr(); 163 for (const auto Symbol : *PS) { 164 // NOTE: `Origin` may be null, and will be stored so in the symbol's 165 // `RefState` in MallocChecker's `RegionState` program state map. 166 State = allocation_state::markReleased(State, Symbol, Origin); 167 } 168 State = State->remove<RawPtrMap>(MR); 169 C.addTransition(State); 170 return; 171 } 172 } 173 174 void InnerPointerChecker::checkFunctionArguments(const CallEvent &Call, 175 ProgramStateRef State, 176 CheckerContext &C) const { 177 if (const auto *FC = dyn_cast<AnyFunctionCall>(&Call)) { 178 const FunctionDecl *FD = FC->getDecl(); 179 if (!FD || !FD->isInStdNamespace()) 180 return; 181 182 for (unsigned I = 0, E = FD->getNumParams(); I != E; ++I) { 183 QualType ParamTy = FD->getParamDecl(I)->getType(); 184 if (!ParamTy->isReferenceType() || 185 ParamTy->getPointeeType().isConstQualified()) 186 continue; 187 188 // In case of member operator calls, `this` is counted as an 189 // argument but not as a parameter. 190 bool isaMemberOpCall = isa<CXXMemberOperatorCall>(FC); 191 unsigned ArgI = isaMemberOpCall ? I+1 : I; 192 193 SVal Arg = FC->getArgSVal(ArgI); 194 const auto *ArgRegion = 195 dyn_cast_or_null<TypedValueRegion>(Arg.getAsRegion()); 196 if (!ArgRegion) 197 continue; 198 199 markPtrSymbolsReleased(Call, State, ArgRegion, C); 200 } 201 } 202 } 203 204 // [string.require] 205 // 206 // "References, pointers, and iterators referring to the elements of a 207 // basic_string sequence may be invalidated by the following uses of that 208 // basic_string object: 209 // 210 // -- As an argument to any standard library function taking a reference 211 // to non-const basic_string as an argument. For example, as an argument to 212 // non-member functions swap(), operator>>(), and getline(), or as an argument 213 // to basic_string::swap(). 214 // 215 // -- Calling non-const member functions, except operator[], at, front, back, 216 // begin, rbegin, end, and rend." 217 218 void InnerPointerChecker::checkPostCall(const CallEvent &Call, 219 CheckerContext &C) const { 220 ProgramStateRef State = C.getState(); 221 222 if (const auto *ICall = dyn_cast<CXXInstanceCall>(&Call)) { 223 if (isCalledOnStringObject(ICall)) { 224 const auto *ObjRegion = dyn_cast_or_null<TypedValueRegion>( 225 ICall->getCXXThisVal().getAsRegion()); 226 227 if (Call.isCalled(CStrFn) || Call.isCalled(DataFn)) { 228 SVal RawPtr = Call.getReturnValue(); 229 if (SymbolRef Sym = RawPtr.getAsSymbol(/*IncludeBaseRegions=*/true)) { 230 // Start tracking this raw pointer by adding it to the set of symbols 231 // associated with this container object in the program state map. 232 233 PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>(); 234 const PtrSet *SetPtr = State->get<RawPtrMap>(ObjRegion); 235 PtrSet Set = SetPtr ? *SetPtr : F.getEmptySet(); 236 assert(C.wasInlined || !Set.contains(Sym)); 237 Set = F.add(Set, Sym); 238 239 State = State->set<RawPtrMap>(ObjRegion, Set); 240 C.addTransition(State); 241 } 242 return; 243 } 244 245 // Check [string.require] / second point. 246 if (isInvalidatingMemberFunction(Call)) { 247 markPtrSymbolsReleased(Call, State, ObjRegion, C); 248 return; 249 } 250 } 251 } 252 253 // Check [string.require] / first point. 254 checkFunctionArguments(Call, State, C); 255 } 256 257 void InnerPointerChecker::checkDeadSymbols(SymbolReaper &SymReaper, 258 CheckerContext &C) const { 259 ProgramStateRef State = C.getState(); 260 PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>(); 261 RawPtrMapTy RPM = State->get<RawPtrMap>(); 262 for (const auto Entry : RPM) { 263 if (!SymReaper.isLiveRegion(Entry.first)) { 264 // Due to incomplete destructor support, some dead regions might 265 // remain in the program state map. Clean them up. 266 State = State->remove<RawPtrMap>(Entry.first); 267 } 268 if (const PtrSet *OldSet = State->get<RawPtrMap>(Entry.first)) { 269 PtrSet CleanedUpSet = *OldSet; 270 for (const auto Symbol : Entry.second) { 271 if (!SymReaper.isLive(Symbol)) 272 CleanedUpSet = F.remove(CleanedUpSet, Symbol); 273 } 274 State = CleanedUpSet.isEmpty() 275 ? State->remove<RawPtrMap>(Entry.first) 276 : State->set<RawPtrMap>(Entry.first, CleanedUpSet); 277 } 278 } 279 C.addTransition(State); 280 } 281 282 namespace clang { 283 namespace ento { 284 namespace allocation_state { 285 286 std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym) { 287 return llvm::make_unique<InnerPointerChecker::InnerPointerBRVisitor>(Sym); 288 } 289 290 const MemRegion *getContainerObjRegion(ProgramStateRef State, SymbolRef Sym) { 291 RawPtrMapTy Map = State->get<RawPtrMap>(); 292 for (const auto Entry : Map) { 293 if (Entry.second.contains(Sym)) { 294 return Entry.first; 295 } 296 } 297 return nullptr; 298 } 299 300 } // end namespace allocation_state 301 } // end namespace ento 302 } // end namespace clang 303 304 std::shared_ptr<PathDiagnosticPiece> 305 InnerPointerChecker::InnerPointerBRVisitor::VisitNode(const ExplodedNode *N, 306 const ExplodedNode *PrevN, 307 BugReporterContext &BRC, 308 BugReport &BR) { 309 if (!isSymbolTracked(N->getState(), PtrToBuf) || 310 isSymbolTracked(PrevN->getState(), PtrToBuf)) 311 return nullptr; 312 313 const Stmt *S = PathDiagnosticLocation::getStmt(N); 314 if (!S) 315 return nullptr; 316 317 const MemRegion *ObjRegion = 318 allocation_state::getContainerObjRegion(N->getState(), PtrToBuf); 319 const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion); 320 QualType ObjTy = TypedRegion->getValueType(); 321 322 SmallString<256> Buf; 323 llvm::raw_svector_ostream OS(Buf); 324 OS << "Pointer to inner buffer of '" << ObjTy.getAsString() 325 << "' obtained here"; 326 PathDiagnosticLocation Pos(S, BRC.getSourceManager(), 327 N->getLocationContext()); 328 return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true, 329 nullptr); 330 } 331 332 void ento::registerInnerPointerChecker(CheckerManager &Mgr) { 333 registerInnerPointerCheckerAux(Mgr); 334 Mgr.registerChecker<InnerPointerChecker>(); 335 } 336