1 //===- OSObjectCStyleCast.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 OSObjectCStyleCast checker, which checks for C-style casts 10 // of OSObjects. Such casts almost always indicate a code smell, 11 // as an explicit static or dynamic cast should be used instead. 12 //===----------------------------------------------------------------------===// 13 14 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 15 #include "clang/ASTMatchers/ASTMatchFinder.h" 16 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" 17 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 18 #include "clang/StaticAnalyzer/Core/Checker.h" 19 #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" 20 #include "llvm/Support/Debug.h" 21 22 using namespace clang; 23 using namespace ento; 24 using namespace ast_matchers; 25 26 namespace { 27 static constexpr const char *const WarnAtNode = "WarnAtNode"; 28 static constexpr const char *const WarnRecordDecl = "WarnRecordDecl"; 29 30 class OSObjectCStyleCastChecker : public Checker<check::ASTCodeBody> { 31 public: 32 void checkASTCodeBody(const Decl *D, AnalysisManager &AM, 33 BugReporter &BR) const; 34 }; 35 } // namespace 36 37 namespace clang { 38 namespace ast_matchers { 39 AST_MATCHER_P(StringLiteral, mentionsBoundType, std::string, BindingID) { 40 return Builder->removeBindings([this, &Node](const BoundNodesMap &Nodes) { 41 const auto &BN = Nodes.getNode(this->BindingID); 42 if (const auto *ND = BN.get<NamedDecl>()) { 43 return ND->getName() != Node.getString(); 44 } 45 return true; 46 }); 47 } 48 } // end namespace ast_matchers 49 } // end namespace clang 50 51 static void emitDiagnostics(const BoundNodes &Nodes, 52 BugReporter &BR, 53 AnalysisDeclContext *ADC, 54 const OSObjectCStyleCastChecker *Checker) { 55 const auto *CE = Nodes.getNodeAs<CastExpr>(WarnAtNode); 56 const CXXRecordDecl *RD = Nodes.getNodeAs<CXXRecordDecl>(WarnRecordDecl); 57 assert(CE && RD); 58 59 std::string Diagnostics; 60 llvm::raw_string_ostream OS(Diagnostics); 61 OS << "C-style cast of an OSObject is prone to type confusion attacks; " 62 << "use 'OSRequiredCast' if the object is definitely of type '" 63 << RD->getNameAsString() << "', or 'OSDynamicCast' followed by " 64 << "a null check if unsure", 65 66 BR.EmitBasicReport( 67 ADC->getDecl(), 68 Checker, 69 /*Name=*/"OSObject C-Style Cast", 70 categories::SecurityError, 71 OS.str(), 72 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), ADC), 73 CE->getSourceRange()); 74 } 75 76 static decltype(auto) hasTypePointingTo(DeclarationMatcher DeclM) { 77 return hasType(pointerType(pointee(hasDeclaration(DeclM)))); 78 } 79 80 void OSObjectCStyleCastChecker::checkASTCodeBody(const Decl *D, 81 AnalysisManager &AM, 82 BugReporter &BR) const { 83 84 AnalysisDeclContext *ADC = AM.getAnalysisDeclContext(D); 85 86 auto DynamicCastM = callExpr(callee(functionDecl(hasName("safeMetaCast")))); 87 // 'allocClassWithName' allocates an object with the given type. 88 // The type is actually provided as a string argument (type's name). 89 // This makes the following pattern possible: 90 // 91 // Foo *object = (Foo *)allocClassWithName("Foo"); 92 // 93 // While OSRequiredCast can be used here, it is still not a useful warning. 94 auto AllocClassWithNameM = callExpr( 95 callee(functionDecl(hasName("allocClassWithName"))), 96 // Here we want to make sure that the string argument matches the 97 // type in the cast expression. 98 hasArgument(0, stringLiteral(mentionsBoundType(WarnRecordDecl)))); 99 100 auto OSObjTypeM = 101 hasTypePointingTo(cxxRecordDecl(isDerivedFrom("OSMetaClassBase"))); 102 auto OSObjSubclassM = hasTypePointingTo( 103 cxxRecordDecl(isDerivedFrom("OSObject")).bind(WarnRecordDecl)); 104 105 auto CastM = 106 cStyleCastExpr( 107 allOf(OSObjSubclassM, 108 hasSourceExpression( 109 allOf(OSObjTypeM, 110 unless(anyOf(DynamicCastM, AllocClassWithNameM)))))) 111 .bind(WarnAtNode); 112 113 auto Matches = 114 match(stmt(forEachDescendant(CastM)), *D->getBody(), AM.getASTContext()); 115 for (BoundNodes Match : Matches) 116 emitDiagnostics(Match, BR, ADC, this); 117 } 118 119 void ento::registerOSObjectCStyleCast(CheckerManager &Mgr) { 120 Mgr.registerChecker<OSObjectCStyleCastChecker>(); 121 } 122 123 bool ento::shouldRegisterOSObjectCStyleCast(const CheckerManager &mgr) { 124 return true; 125 } 126