|
| 1 | +//=== StoreToImmutableChecker.cpp - Store to immutable memory ---*- 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 StoreToImmutableChecker, a checker that detects writes |
| 10 | +// to immutable memory regions. This implements part of SEI CERT Rule ENV30-C. |
| 11 | +// |
| 12 | +//===----------------------------------------------------------------------===// |
| 13 | + |
| 14 | +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| 15 | +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
| 16 | +#include "clang/StaticAnalyzer/Core/Checker.h" |
| 17 | +#include "clang/StaticAnalyzer/Core/CheckerManager.h" |
| 18 | +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| 19 | +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" |
| 20 | + |
| 21 | +using namespace clang; |
| 22 | +using namespace ento; |
| 23 | + |
| 24 | +namespace { |
| 25 | +class StoreToImmutableChecker : public Checker<check::Bind> { |
| 26 | + const BugType BT{this, "Write to immutable memory", "CERT Environment (ENV)"}; |
| 27 | + |
| 28 | +public: |
| 29 | + void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const; |
| 30 | +}; |
| 31 | +} // end anonymous namespace |
| 32 | + |
| 33 | +static bool isInitializationContext(const Stmt *S, CheckerContext &C) { |
| 34 | + // Check if this is a DeclStmt (variable declaration) |
| 35 | + if (isa<DeclStmt>(S)) |
| 36 | + return true; |
| 37 | + |
| 38 | + // This part is specific for initialization of const lambdas pre-C++17. |
| 39 | + // Lets look at the AST of the statement: |
| 40 | + // ``` |
| 41 | + // const auto lambda = [](){}; |
| 42 | + // ``` |
| 43 | + // |
| 44 | + // The relevant part of the AST for this case prior to C++17 is: |
| 45 | + // ... |
| 46 | + // `-DeclStmt |
| 47 | + // `-VarDecl |
| 48 | + // `-ExprWithCleanups |
| 49 | + // `-CXXConstructExpr |
| 50 | + // ... |
| 51 | + // In C++17 and later, the AST is different: |
| 52 | + // ... |
| 53 | + // `-DeclStmt |
| 54 | + // `-VarDecl |
| 55 | + // `-ImplicitCastExpr |
| 56 | + // `-LambdaExpr |
| 57 | + // |-CXXRecordDecl |
| 58 | + // `-CXXConstructExpr |
| 59 | + // ... |
| 60 | + // And even beside this, the statement `S` that is given to the checkBind |
| 61 | + // callback is the VarDecl in C++17 and later, and the CXXConstructExpr in |
| 62 | + // C++14 and before. So in order to support the C++14 we need the following |
| 63 | + // ugly hack to detect whether this construction is used to initialize a |
| 64 | + // variable. |
| 65 | + // |
| 66 | + // FIXME: This should be eliminated by improving the API of checkBind to |
| 67 | + // ensure that it consistently passes the `VarDecl` (instead of the |
| 68 | + // `CXXConstructExpr`) when the constructor call denotes the initialization |
| 69 | + // of a variable with a lambda, or maybe less preferably, try the more |
| 70 | + // invasive approach of passing the information forward to the checkers |
| 71 | + // whether the current bind is an initialization or an assignment. |
| 72 | + const auto *ConstructExp = dyn_cast<CXXConstructExpr>(S); |
| 73 | + return ConstructExp && ConstructExp->isElidable(); |
| 74 | +} |
| 75 | + |
| 76 | +static bool isEffectivelyConstRegion(const MemRegion *MR, CheckerContext &C) { |
| 77 | + if (isa<GlobalImmutableSpaceRegion>(MR)) |
| 78 | + return true; |
| 79 | + |
| 80 | + // Check if this is a TypedRegion with a const-qualified type |
| 81 | + if (const auto *TR = dyn_cast<TypedRegion>(MR)) { |
| 82 | + QualType LocationType = TR->getDesugaredLocationType(C.getASTContext()); |
| 83 | + if (LocationType->isPointerOrReferenceType()) |
| 84 | + LocationType = LocationType->getPointeeType(); |
| 85 | + if (LocationType.isConstQualified()) |
| 86 | + return true; |
| 87 | + } |
| 88 | + |
| 89 | + // Check if this is a SymbolicRegion with a const-qualified pointee type |
| 90 | + if (const auto *SR = dyn_cast<SymbolicRegion>(MR)) { |
| 91 | + QualType PointeeType = SR->getPointeeStaticType(); |
| 92 | + if (PointeeType.isConstQualified()) |
| 93 | + return true; |
| 94 | + } |
| 95 | + |
| 96 | + // NOTE: The above branches do not cover AllocaRegion. We do not need to check |
| 97 | + // AllocaRegion, as it models untyped memory, that is allocated on the stack. |
| 98 | + |
| 99 | + return false; |
| 100 | +} |
| 101 | + |
| 102 | +static const MemRegion *getInnermostConstRegion(const MemRegion *MR, |
| 103 | + CheckerContext &C) { |
| 104 | + while (true) { |
| 105 | + if (isEffectivelyConstRegion(MR, C)) |
| 106 | + return MR; |
| 107 | + if (auto *SR = dyn_cast<SubRegion>(MR)) |
| 108 | + MR = SR->getSuperRegion(); |
| 109 | + else |
| 110 | + return nullptr; |
| 111 | + } |
| 112 | +} |
| 113 | + |
| 114 | +static const DeclRegion * |
| 115 | +getInnermostEnclosingConstDeclRegion(const MemRegion *MR, CheckerContext &C) { |
| 116 | + while (true) { |
| 117 | + if (const auto *DR = dyn_cast<DeclRegion>(MR)) { |
| 118 | + const ValueDecl *D = DR->getDecl(); |
| 119 | + QualType DeclaredType = D->getType(); |
| 120 | + if (DeclaredType.isConstQualified()) |
| 121 | + return DR; |
| 122 | + } |
| 123 | + if (auto *SR = dyn_cast<SubRegion>(MR)) |
| 124 | + MR = SR->getSuperRegion(); |
| 125 | + else |
| 126 | + return nullptr; |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +void StoreToImmutableChecker::checkBind(SVal Loc, SVal Val, const Stmt *S, |
| 131 | + CheckerContext &C) const { |
| 132 | + // We are only interested in stores to memory regions |
| 133 | + const MemRegion *MR = Loc.getAsRegion(); |
| 134 | + if (!MR) |
| 135 | + return; |
| 136 | + |
| 137 | + // Skip variable declarations and initializations - we only want to catch |
| 138 | + // actual writes |
| 139 | + // FIXME: If the API of checkBind would allow to distinguish between |
| 140 | + // initialization and assignment, we could use that instead. |
| 141 | + if (isInitializationContext(S, C)) |
| 142 | + return; |
| 143 | + |
| 144 | + // Check if the region is in the global immutable space |
| 145 | + const MemSpaceRegion *MS = MR->getMemorySpace(C.getState()); |
| 146 | + const bool IsGlobalImmutableSpace = isa<GlobalImmutableSpaceRegion>(MS); |
| 147 | + // Check if the region corresponds to a const variable |
| 148 | + const MemRegion *InnermostConstRegion = getInnermostConstRegion(MR, C); |
| 149 | + if (!IsGlobalImmutableSpace && !InnermostConstRegion) |
| 150 | + return; |
| 151 | + |
| 152 | + SmallString<64> WarningMessage{"Trying to write to immutable memory"}; |
| 153 | + if (IsGlobalImmutableSpace) |
| 154 | + WarningMessage += " in global read-only storage"; |
| 155 | + |
| 156 | + // Generate the bug report |
| 157 | + ExplodedNode *N = C.generateNonFatalErrorNode(); |
| 158 | + if (!N) |
| 159 | + return; |
| 160 | + |
| 161 | + auto R = std::make_unique<PathSensitiveBugReport>(BT, WarningMessage, N); |
| 162 | + R->addRange(S->getSourceRange()); |
| 163 | + |
| 164 | + // Generate a note if the ___location that is being written to has a |
| 165 | + // declaration or if it is a subregion of a const region with a declaration. |
| 166 | + const DeclRegion *DR = |
| 167 | + getInnermostEnclosingConstDeclRegion(InnermostConstRegion, C); |
| 168 | + if (DR) { |
| 169 | + const char *NoteMessage = |
| 170 | + (DR != MR) ? "Enclosing memory region is declared as immutable here" |
| 171 | + : "Memory region is declared as immutable here"; |
| 172 | + R->addNote(NoteMessage, PathDiagnosticLocation::create( |
| 173 | + DR->getDecl(), C.getSourceManager())); |
| 174 | + } |
| 175 | + |
| 176 | + // For this checker, we are only interested in the value being written, no |
| 177 | + // need to mark the value being assigned interesting. |
| 178 | + |
| 179 | + C.emitReport(std::move(R)); |
| 180 | +} |
| 181 | + |
| 182 | +void ento::registerStoreToImmutableChecker(CheckerManager &mgr) { |
| 183 | + mgr.registerChecker<StoreToImmutableChecker>(); |
| 184 | +} |
| 185 | + |
| 186 | +bool ento::shouldRegisterStoreToImmutableChecker(const CheckerManager &mgr) { |
| 187 | + return true; |
| 188 | +} |
0 commit comments