aboutsummaryrefslogtreecommitdiff
path: root/clang/lib/StaticAnalyzer/Checkers/StoreToImmutableChecker.cpp
blob: 2bb391799d696ec07ed71b57bc5d49ed130b891b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
//=== StoreToImmutableChecker.cpp - Store to immutable memory ---*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines StoreToImmutableChecker, a checker that detects writes
// to immutable memory regions. This implements part of SEI CERT Rule ENV30-C.
//
//===----------------------------------------------------------------------===//

#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"

using namespace clang;
using namespace ento;

namespace {
class StoreToImmutableChecker : public Checker<check::Bind> {
  const BugType BT{this, "Write to immutable memory", "CERT Environment (ENV)"};

public:
  void checkBind(SVal Loc, SVal Val, const Stmt *S, bool AtDeclInit,
                 CheckerContext &C) const;
};
} // end anonymous namespace

static bool isEffectivelyConstRegion(const MemRegion *MR, CheckerContext &C) {
  if (isa<GlobalImmutableSpaceRegion>(MR))
    return true;

  // Check if this is a TypedRegion with a const-qualified type
  if (const auto *TR = dyn_cast<TypedRegion>(MR)) {
    QualType LocationType = TR->getDesugaredLocationType(C.getASTContext());
    if (LocationType->isPointerOrReferenceType())
      LocationType = LocationType->getPointeeType();
    if (LocationType.isConstQualified())
      return true;
  }

  // Check if this is a SymbolicRegion with a const-qualified pointee type
  if (const auto *SR = dyn_cast<SymbolicRegion>(MR)) {
    QualType PointeeType = SR->getPointeeStaticType();
    if (PointeeType.isConstQualified())
      return true;
  }

  // NOTE: The above branches do not cover AllocaRegion. We do not need to check
  // AllocaRegion, as it models untyped memory, that is allocated on the stack.

  return false;
}

static const MemRegion *getInnermostConstRegion(const MemRegion *MR,
                                                CheckerContext &C) {
  while (true) {
    if (isEffectivelyConstRegion(MR, C))
      return MR;
    if (auto *SR = dyn_cast<SubRegion>(MR))
      MR = SR->getSuperRegion();
    else
      return nullptr;
  }
}

static const DeclRegion *
getInnermostEnclosingConstDeclRegion(const MemRegion *MR, CheckerContext &C) {
  while (true) {
    if (const auto *DR = dyn_cast<DeclRegion>(MR)) {
      const ValueDecl *D = DR->getDecl();
      QualType DeclaredType = D->getType();
      if (DeclaredType.isConstQualified())
        return DR;
    }
    if (auto *SR = dyn_cast<SubRegion>(MR))
      MR = SR->getSuperRegion();
    else
      return nullptr;
  }
}

void StoreToImmutableChecker::checkBind(SVal Loc, SVal Val, const Stmt *S,
                                        bool AtDeclInit,
                                        CheckerContext &C) const {
  // We are only interested in stores to memory regions
  const MemRegion *MR = Loc.getAsRegion();
  if (!MR)
    return;

  // Skip variable declarations and initializations - we only want to catch
  // actual writes
  if (AtDeclInit)
    return;

  // Check if the region is in the global immutable space
  const MemSpaceRegion *MS = MR->getMemorySpace(C.getState());
  const bool IsGlobalImmutableSpace = isa<GlobalImmutableSpaceRegion>(MS);
  // Check if the region corresponds to a const variable
  const MemRegion *InnermostConstRegion = getInnermostConstRegion(MR, C);
  if (!IsGlobalImmutableSpace && !InnermostConstRegion)
    return;

  SmallString<64> WarningMessage{"Trying to write to immutable memory"};
  if (IsGlobalImmutableSpace)
    WarningMessage += " in global read-only storage";

  // Generate the bug report
  ExplodedNode *N = C.generateNonFatalErrorNode();
  if (!N)
    return;

  auto R = std::make_unique<PathSensitiveBugReport>(BT, WarningMessage, N);
  R->addRange(S->getSourceRange());

  // Generate a note if the location that is being written to has a
  // declaration or if it is a subregion of a const region with a declaration.
  const DeclRegion *DR =
      getInnermostEnclosingConstDeclRegion(InnermostConstRegion, C);
  if (DR) {
    const char *NoteMessage =
        (DR != MR) ? "Enclosing memory region is declared as immutable here"
                   : "Memory region is declared as immutable here";
    R->addNote(NoteMessage, PathDiagnosticLocation::create(
                                DR->getDecl(), C.getSourceManager()));
  }

  // For this checker, we are only interested in the value being written, no
  // need to mark the value being assigned interesting.

  C.emitReport(std::move(R));
}

void ento::registerStoreToImmutableChecker(CheckerManager &mgr) {
  mgr.registerChecker<StoreToImmutableChecker>();
}

bool ento::shouldRegisterStoreToImmutableChecker(const CheckerManager &mgr) {
  return true;
}