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(Predicate: [this, &Node](const BoundNodesMap &Nodes) { |
41 | const auto &BN = Nodes.getNode(ID: 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>(ID: WarnAtNode); |
56 | const CXXRecordDecl *RD = Nodes.getNodeAs<CXXRecordDecl>(ID: 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(InnerMatcher: pointerType(pointee(hasDeclaration(InnerMatcher: 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(InnerMatcher: functionDecl(hasName(Name: "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(InnerMatcher: functionDecl(hasName(Name: "allocClassWithName" ))), |
96 | // Here we want to make sure that the string argument matches the |
97 | // type in the cast expression. |
98 | hasArgument(N: 0, InnerMatcher: stringLiteral(mentionsBoundType(BindingID: WarnRecordDecl)))); |
99 | |
100 | auto OSObjTypeM = |
101 | hasTypePointingTo(DeclM: cxxRecordDecl(isDerivedFrom(BaseName: "OSMetaClassBase" ))); |
102 | auto OSObjSubclassM = hasTypePointingTo( |
103 | DeclM: cxxRecordDecl(isDerivedFrom(BaseName: "OSObject" )).bind(ID: WarnRecordDecl)); |
104 | |
105 | auto CastM = |
106 | cStyleCastExpr( |
107 | allOf(OSObjSubclassM, |
108 | hasSourceExpression( |
109 | InnerMatcher: allOf(OSObjTypeM, |
110 | unless(anyOf(DynamicCastM, AllocClassWithNameM)))))) |
111 | .bind(ID: WarnAtNode); |
112 | |
113 | auto Matches = |
114 | match(Matcher: stmt(forEachDescendant(CastM)), Node: *D->getBody(), Context&: AM.getASTContext()); |
115 | for (BoundNodes Match : Matches) |
116 | emitDiagnostics(Nodes: Match, BR, ADC, Checker: 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 | |