126 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines OSObjectCStyleCast checker, which checks for C-style casts10// 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 22using namespace clang;23using namespace ento;24using namespace ast_matchers;25 26namespace {27static constexpr const char *const WarnAtNode = "WarnAtNode";28static constexpr const char *const WarnRecordDecl = "WarnRecordDecl";29 30class OSObjectCStyleCastChecker : public Checker<check::ASTCodeBody> {31public:32 void checkASTCodeBody(const Decl *D, AnalysisManager &AM,33 BugReporter &BR) const;34};35} // namespace36 37namespace clang {38namespace ast_matchers {39AST_MATCHER_P(StringLiteral, mentionsBoundType, std::string, BindingID) {40 return Builder->removeBindings([this, &Node](const BoundNodesMap &Nodes) {41 const DynTypedNode &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_matchers49} // end namespace clang50 51static 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 Diagnostics,72 PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), ADC),73 CE->getSourceRange());74}75 76static decltype(auto) hasTypePointingTo(DeclarationMatcher DeclM) {77 return hasType(pointerType(pointee(hasDeclaration(DeclM))));78}79 80void 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 the97 // 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 119void ento::registerOSObjectCStyleCast(CheckerManager &Mgr) {120 Mgr.registerChecker<OSObjectCStyleCastChecker>();121}122 123bool ento::shouldRegisterOSObjectCStyleCast(const CheckerManager &mgr) {124 return true;125}126