Skip to content

Commit a3d0c54

Browse files
authored
[clang][analyzer] Add StoreToImmutable checker (#150417)
This adds alpha.core.StoreToImmutable, a new alpha checker that detects writes to immutable memory regions, implementing part of SEI CERT Rule ENV30-C. The original proposal only handled global const variables, but this implementation extends it to also detect writes to: - Local const variables - String literals - Const parameters and struct members - Const arrays and pointers to const data This checker is the continuation of the work started by zukatsinadze. Discussion: https://reviews.llvm.org/D124244
1 parent 3a20c00 commit a3d0c54

File tree

8 files changed

+438
-0
lines changed

8 files changed

+438
-0
lines changed

clang/docs/analyzer/checkers.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3086,6 +3086,23 @@ Either the comparison is useless or there is division by zero.
30863086
if (x == 0) { } // warn
30873087
}
30883088
3089+
.. _alpha-core-StoreToImmutable:
3090+
3091+
alpha.core.StoreToImmutable (C, C++)
3092+
""""""""""""""""""""""""""""""""""""
3093+
Check for writes to immutable memory regions. This implements part of SEI CERT Rule ENV30-C.
3094+
3095+
This checker detects attempts to write to memory regions that are marked as immutable,
3096+
including const variables, string literals, and other const-qualified memory.
3097+
3098+
.. literalinclude:: checkers/storetoimmutable_example.cpp
3099+
:language: cpp
3100+
3101+
**Solution**
3102+
3103+
Avoid writing to const-qualified memory regions. If you need to modify the data,
3104+
remove the const qualifier from the original declaration or use a mutable copy.
3105+
30893106
alpha.cplusplus
30903107
^^^^^^^^^^^^^^^
30913108
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const int global_const = 42;
2+
3+
struct TestStruct {
4+
const int x;
5+
int y;
6+
};
7+
8+
void immutable_violation_examples() {
9+
*(int *)&global_const = 100; // warn: Trying to write to immutable memory
10+
11+
const int local_const = 42;
12+
*(int *)&local_const = 43; // warn: Trying to write to immutable memory
13+
14+
// NOTE: The following is reported in C++, but not in C, as the analyzer
15+
// treats string literals as non-const char arrays in C mode.
16+
char *ptr_to_str_literal = (char *)"hello";
17+
ptr_to_str_literal[0] = 'H'; // warn: Trying to write to immutable memory
18+
19+
TestStruct s = {1, 2};
20+
*(int *)&s.x = 10; // warn: Trying to write to immutable memory
21+
}

clang/include/clang/StaticAnalyzer/Checkers/Checkers.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,11 @@ def StackAddrAsyncEscapeChecker
297297
"Check that addresses to stack memory do not escape the function">,
298298
Documentation<HasDocumentation>;
299299

300+
def StoreToImmutableChecker : Checker<"StoreToImmutable">,
301+
HelpText<"Check for writes to immutable memory regions. "
302+
"This implements part of SEI CERT Rule ENV30-C.">,
303+
Documentation<HasDocumentation>;
304+
300305
def PthreadLockBase : Checker<"PthreadLockBase">,
301306
HelpText<"Helper registering multiple checks.">,
302307
Documentation<NotDocumented>,

clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ add_clang_library(clangStaticAnalyzerCheckers
104104
SmartPtrChecker.cpp
105105
SmartPtrModeling.cpp
106106
StackAddrEscapeChecker.cpp
107+
StoreToImmutableChecker.cpp
107108
StdLibraryFunctionsChecker.cpp
108109
StdVariantChecker.cpp
109110
STLAlgorithmModeling.cpp
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
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+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.core.StoreToImmutable -verify %s
2+
3+
// Test basic functionality of StoreToImmutable checker for the C programming language.
4+
5+
const int tentative_global_const; // expected-note {{Memory region is declared as immutable here}}
6+
7+
void test_direct_write_to_tentative_const_global() {
8+
*(int*)&tentative_global_const = 100; // expected-warning {{Trying to write to immutable memory in global read-only storage}}
9+
}
10+
11+
const int global_const = 42; // expected-note {{Memory region is declared as immutable here}}
12+
13+
void test_direct_write_to_const_global() {
14+
// This should trigger a warning about writing to immutable memory
15+
*(int*)&global_const = 100; // expected-warning {{Trying to write to immutable memory in global read-only storage}}
16+
}
17+
18+
void test_write_through_const_pointer() {
19+
const int local_const = 10; // expected-note {{Memory region is declared as immutable here}}
20+
int *ptr = (int*)&local_const;
21+
*ptr = 20; // expected-warning {{Trying to write to immutable memory}}
22+
}
23+
24+
void test_write_to_const_array() {
25+
const int arr[5] = {1, 2, 3, 4, 5}; // expected-note {{Enclosing memory region is declared as immutable here}}
26+
int *ptr = (int*)arr;
27+
ptr[0] = 10; // expected-warning {{Trying to write to immutable memory}}
28+
}
29+
30+
struct TestStruct {
31+
const int x; // expected-note 2 {{Memory region is declared as immutable here}}
32+
int y;
33+
};
34+
35+
void test_write_to_const_struct_member() {
36+
struct TestStruct s = {1, 2};
37+
int *ptr = (int*)&s.x;
38+
*ptr = 10; // expected-warning {{Trying to write to immutable memory}}
39+
}
40+
41+
const int global_array[3] = {1, 2, 3}; // expected-note {{Enclosing memory region is declared as immutable here}}
42+
43+
void test_write_to_const_global_array() {
44+
int *ptr = (int*)global_array;
45+
ptr[0] = 10; // expected-warning {{Trying to write to immutable memory in global read-only storage}}
46+
}
47+
48+
const struct TestStruct global_struct = {1, 2};
49+
50+
void test_write_to_const_global_struct() {
51+
int *ptr = (int*)&global_struct.x;
52+
*ptr = 10; // expected-warning {{Trying to write to immutable memory in global read-only storage}}
53+
}
54+
55+
56+
void test_write_to_const_param(const int param) { // expected-note {{Memory region is declared as immutable here}}
57+
*(int*)&param = 100; // expected-warning {{Trying to write to immutable memory}}
58+
}
59+
60+
void test_write_to_const_ptr_param(const int *param) {
61+
*(int*)param = 100; // expected-warning {{Trying to write to immutable memory}}
62+
}
63+
64+
void test_write_to_const_array_param(const int arr[5]) {
65+
*(int*)arr = 100; // expected-warning {{Trying to write to immutable memory}}
66+
}
67+
68+
struct ParamStruct {
69+
const int z; // expected-note 2 {{Memory region is declared as immutable here}}
70+
int w;
71+
};
72+
73+
void test_write_to_const_struct_param(const struct ParamStruct s) {
74+
*(int*)&s.z = 100; // expected-warning {{Trying to write to immutable memory}}
75+
}
76+
77+
void test_write_to_const_struct_ptr_param(const struct ParamStruct *s) {
78+
*(int*)&s->z = 100; // expected-warning {{Trying to write to immutable memory}}
79+
}
80+
81+
void test_write_to_nonconst() {
82+
int non_const = 42;
83+
*(int*)&non_const = 100; // No warning expected
84+
}
85+
86+
int global_non_const = 42;
87+
88+
void test_write_to_nonconst_global() {
89+
*(int*)&global_non_const = 100; // No warning expected
90+
}
91+
92+
struct NonConstStruct {
93+
int x;
94+
int y;
95+
};
96+
97+
void test_write_to_nonconst_struct_member() {
98+
struct NonConstStruct s = {1, 2};
99+
*(int*)&s.x = 100; // No warning expected
100+
}
101+
102+
void test_write_to_nonconst_param(int param) {
103+
*(int*)&param = 100; // No warning expected
104+
}
105+
106+
void test_normal_assignment() {
107+
int x = 42;
108+
x = 100; // No warning expected
109+
}
110+
111+
void test_const_ptr_to_nonconst_data() {
112+
int data = 42;
113+
const int *ptr = &data;
114+
*(int*)ptr = 100; // No warning expected
115+
}
116+
117+
void test_const_ptr_to_const_data() {
118+
const int data = 42; // expected-note {{Memory region is declared as immutable here}}
119+
const int *ptr = &data;
120+
*(int*)ptr = 100; // expected-warning {{Trying to write to immutable memory}}
121+
}

0 commit comments

Comments
 (0)