Skip to content

Commit bd99602

Browse files
eupharinaresistor
authored andcommitted
[CHERI_CSA] New cheri.Allocation checker
1 parent 9db81c9 commit bd99602

File tree

7 files changed

+292
-15
lines changed

7 files changed

+292
-15
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,4 +1836,10 @@ let ParentPackage = CHERI in {
18361836

18371837
let ParentPackage = CHERIAlpha in {
18381838

1839+
def AllocationChecker
1840+
: Checker<"Allocation">,
1841+
HelpText<
1842+
"Suggest narrowing bounds for escaping suballocation capabilities">,
1843+
Documentation<NotDocumented>;
1844+
18391845
} // end alpha.cheri
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
//===-- AllocationChecker.cpp - Allocation Checker -*- 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 checker that detects unrelated objects being allocated as
10+
// adjacent suballocations of the bigger memory allocation. It reports
11+
// situations when an address of such object escapes the function and
12+
// suggests narrowing the bounds of the escaping capability, so it covers only
13+
// the current suballocation, following the principle of least privilege.
14+
//
15+
//===----------------------------------------------------------------------===//
16+
#include "CHERIUtils.h"
17+
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18+
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19+
#include "clang/StaticAnalyzer/Core/Checker.h"
20+
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
21+
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
22+
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
23+
24+
using namespace clang;
25+
using namespace ento;
26+
using namespace cheri;
27+
28+
namespace {
29+
30+
class AllocationChecker : public Checker<check::PostStmt<CastExpr>> {
31+
BugType BT_1{this, "Allocation partitioning", "CHERI portability"};
32+
33+
class AllocPartitionBugVisitor : public BugReporterVisitor {
34+
public:
35+
AllocPartitionBugVisitor(const MemRegion *R) : Reg(R) {}
36+
37+
void Profile(llvm::FoldingSetNodeID &ID) const override {
38+
static int X = 0;
39+
ID.AddPointer(&X);
40+
ID.AddPointer(Reg);
41+
}
42+
43+
PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
44+
BugReporterContext &BRC,
45+
PathSensitiveBugReport &BR) override;
46+
47+
private:
48+
const MemRegion *Reg;
49+
};
50+
51+
public:
52+
void checkPostStmt(const CastExpr *CE, CheckerContext &C) const;
53+
54+
private:
55+
ExplodedNode *emitAllocationPartitionWarning(const CastExpr *CE,
56+
CheckerContext &C,
57+
const MemRegion *R) const;
58+
};
59+
60+
} // namespace
61+
62+
REGISTER_MAP_WITH_PROGRAMSTATE(AllocMap, const MemRegion *, QualType)
63+
REGISTER_MAP_WITH_PROGRAMSTATE(ShiftMap, const MemRegion *, const MemRegion *)
64+
65+
namespace {
66+
std::pair<const MemRegion *, bool> getAllocationStart(const MemRegion *R,
67+
CheckerContext &C,
68+
bool ZeroShift = true) {
69+
if (const ElementRegion *ER = R->getAs<ElementRegion>()) {
70+
const MemRegion *Base = ER->getSuperRegion();
71+
return getAllocationStart(Base, C, ER->getIndex().isZeroConstant());
72+
}
73+
if (auto OrigR = C.getState()->get<ShiftMap>(R)) {
74+
return std::make_pair(*OrigR, false);
75+
}
76+
return std::make_pair(R, ZeroShift);
77+
}
78+
79+
bool isAllocation(const MemRegion *R) {
80+
if (R->getAs<SymbolicRegion>())
81+
return true;
82+
if (const TypedValueRegion *TR = R->getAs<TypedValueRegion>()) {
83+
return TR->getValueType()->isArrayType();
84+
}
85+
return false;
86+
}
87+
88+
bool relatedTypes(const Type *Ty1, const Type *Ty2) {
89+
if (Ty1 == Ty2)
90+
return true;
91+
if (Ty1->isArrayType())
92+
return relatedTypes(Ty1->getArrayElementTypeNoTypeQual(), Ty2);
93+
return false;
94+
}
95+
96+
bool isGenPtrType(QualType Ty) {
97+
return Ty->isVoidPointerType() ||
98+
((Ty->isPointerType() || Ty->isArrayType()) &&
99+
Ty->getPointeeOrArrayElementType()->isCharType());
100+
}
101+
102+
std::optional<QualType> getPrevType(ProgramStateRef State, const MemRegion *R) {
103+
if (const QualType *PrevTy = State->get<AllocMap>(R))
104+
return *PrevTy;
105+
if (const TypedValueRegion *TR = R->getAs<TypedValueRegion>()) {
106+
QualType Ty = TR->getValueType();
107+
if ((Ty->isPointerType() || Ty->isArrayType()) && !isGenPtrType(Ty))
108+
return Ty;
109+
}
110+
return std::nullopt;
111+
}
112+
113+
} // namespace
114+
115+
ExplodedNode *AllocationChecker::emitAllocationPartitionWarning(
116+
const CastExpr *CE, CheckerContext &C, const MemRegion *MR) const {
117+
if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) {
118+
SmallString<256> Buf;
119+
llvm::raw_svector_ostream OS(Buf);
120+
OS << "Allocation partition: ";
121+
describeCast(OS, CE, C.getASTContext().getLangOpts());
122+
auto R = std::make_unique<PathSensitiveBugReport>(BT_1, OS.str(), ErrNode);
123+
R->addVisitor(std::make_unique<AllocPartitionBugVisitor>(MR));
124+
C.emitReport(std::move(R));
125+
return ErrNode;
126+
}
127+
return nullptr;
128+
}
129+
130+
void AllocationChecker::checkPostStmt(const CastExpr *CE,
131+
CheckerContext &C) const {
132+
if (CE->getCastKind() != CK_BitCast)
133+
return;
134+
SVal SrcVal = C.getSVal(CE->getSubExpr());
135+
const MemRegion *MR = SrcVal.getAsRegion();
136+
if (!MR)
137+
return;
138+
if (MR->getMemorySpace()->getKind() == MemSpaceRegion::CodeSpaceRegionKind)
139+
return;
140+
141+
ProgramStateRef State = C.getState();
142+
bool Updated = false;
143+
144+
std::pair<const MemRegion *, bool> StartPair = getAllocationStart(MR, C);
145+
const MemRegion *SR = StartPair.first;
146+
if (!isAllocation(SR))
147+
return;
148+
bool ZeroShift = StartPair.second;
149+
150+
SVal DstVal = C.getSVal(CE);
151+
const MemRegion *DMR = DstVal.getAsRegion();
152+
if (MR->getAs<ElementRegion>() && (!DMR || !DMR->getAs<ElementRegion>())) {
153+
if (DstVal.isUnknown()) {
154+
const LocationContext *LCtx = C.getLocationContext();
155+
DstVal = C.getSValBuilder().conjureSymbolVal(
156+
nullptr, CE, LCtx, CE->getType(), C.blockCount());
157+
State = State->BindExpr(CE, LCtx, DstVal);
158+
DMR = DstVal.getAsRegion();
159+
}
160+
if (DMR) {
161+
State = State->set<ShiftMap>(DMR, SR);
162+
Updated = true;
163+
}
164+
}
165+
166+
QualType DstTy = CE->getType().getUnqualifiedType();
167+
if (!DstTy->isPointerType())
168+
return;
169+
if (DstTy->isVoidPointerType() || DstTy->getPointeeType()->isCharType())
170+
return;
171+
172+
std::optional<QualType> PrevTy = getPrevType(State, SR);
173+
if (PrevTy) {
174+
if (SR != MR && !ZeroShift) {
175+
const Type *Ty1 = PrevTy->getTypePtr()
176+
->getPointeeOrArrayElementType()
177+
->getUnqualifiedDesugaredType();
178+
const Type *Ty2 = DstTy->getPointeeType()->getUnqualifiedDesugaredType();
179+
if (!relatedTypes(Ty1, Ty2)) {
180+
emitAllocationPartitionWarning(CE, C, SR);
181+
return;
182+
} // else OK
183+
} // else ??? (ignore for now)
184+
} else {
185+
State = State->set<AllocMap>(SR, DstTy);
186+
Updated = true;
187+
}
188+
189+
if (Updated)
190+
C.addTransition(State);
191+
}
192+
193+
PathDiagnosticPieceRef AllocationChecker::AllocPartitionBugVisitor::VisitNode(
194+
const ExplodedNode *N, BugReporterContext &BRC,
195+
PathSensitiveBugReport &BR) {
196+
const Stmt *S = N->getStmtForDiagnostics();
197+
if (!S)
198+
return nullptr;
199+
200+
const CastExpr *CE = dyn_cast<CastExpr>(S);
201+
if (!CE || CE->getCastKind() != CK_BitCast)
202+
return nullptr;
203+
204+
if (Reg != N->getSVal(CE->getSubExpr()).getAsRegion())
205+
return nullptr;
206+
207+
SmallString<256> Buf;
208+
llvm::raw_svector_ostream OS(Buf);
209+
OS << "Previous partition: ";
210+
describeCast(OS, CE, BRC.getASTContext().getLangOpts());
211+
212+
// Generate the extra diagnostic.
213+
PathDiagnosticLocation const Pos(S, BRC.getSourceManager(),
214+
N->getLocationContext());
215+
return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true);
216+
}
217+
218+
//===----------------------------------------------------------------------===//
219+
// Checker registration.
220+
//===----------------------------------------------------------------------===//
221+
222+
void ento::registerAllocationChecker(CheckerManager &Mgr) {
223+
Mgr.registerChecker<AllocationChecker>();
224+
}
225+
226+
bool ento::shouldRegisterAllocationChecker(const CheckerManager &Mgr) {
227+
return true;
228+
}

clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ bool hasCapability(const QualType OrigTy, ASTContext &Ctx) {
5252
return false;
5353
}
5454

55+
void describeCast(raw_ostream &OS, const CastExpr *CE,
56+
const LangOptions &LangOpts) {
57+
OS << (dyn_cast<ImplicitCastExpr>(CE) ? "implicit" : "explicit");
58+
OS << " cast from '";
59+
CE->getSubExpr()->getType().print(OS, PrintingPolicy(LangOpts));
60+
OS << "' to '";
61+
CE->getType().print(OS, PrintingPolicy(LangOpts));
62+
OS << "'";
63+
}
64+
5565
} // namespace cheri
5666
} // namespace ento
5767
} // namespace clang

clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ bool isGenericPointerType(const QualType T, bool AcceptCharPtr = true);
2727

2828
bool hasCapability(const QualType OrigTy, ASTContext &Ctx);
2929

30+
void describeCast(raw_ostream &OS, const CastExpr *CE,
31+
const LangOptions &LangOpts);
32+
3033
} // namespace cheri
3134
} // namespace ento
3235
} // namespace clang

clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -532,20 +532,6 @@ void ProvenanceSourceChecker::checkDeadSymbols(SymbolReaper &SymReaper,
532532
C.addTransition(State);
533533
}
534534

535-
namespace {
536-
537-
static void describeCast(raw_ostream &OS, const CastExpr *CE,
538-
const LangOptions &LangOpts) {
539-
OS << (dyn_cast<ImplicitCastExpr>(CE) ? "implicit" : "explicit");
540-
OS << " cast from '";
541-
CE->getSubExpr()->getType().print(OS, PrintingPolicy(LangOpts));
542-
OS << "' to '";
543-
CE->getType().print(OS, PrintingPolicy(LangOpts));
544-
OS << "'";
545-
}
546-
547-
} // namespace
548-
549535
PathDiagnosticPieceRef ProvenanceSourceChecker::InvalidCapBugVisitor::VisitNode(
550536
const ExplodedNode *N, BugReporterContext &BRC,
551537
PathSensitiveBugReport &BR) {

clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ add_clang_library(clangStaticAnalyzerCheckers
2525
CheckPlacementNew.cpp
2626
CheckSecuritySyntaxOnly.cpp
2727
CheckerDocumentation.cpp
28+
CHERI/AllocationChecker.cpp
29+
CHERI/CapabilityCopyChecker.cpp
2830
CHERI/CHERIUtils.cpp
2931
CHERI/PointerSizeAssumptionsChecker.cpp
3032
CHERI/ProvenanceSourceChecker.cpp
31-
CHERI/CapabilityCopyChecker.cpp
3233
CHERI/SubObjectRepresentabilityChecker.cpp
3334
ChrootChecker.cpp
3435
CloneChecker.cpp
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: %cheri_purecap_cc1 -analyze -verify %s \
2+
// RUN: -analyzer-checker=core,unix,alpha.cheri.Allocation
3+
4+
typedef __typeof__(sizeof(int)) size_t;
5+
extern void * malloc(size_t);
6+
7+
8+
struct S1 {
9+
int *a[3];
10+
int *d[1];
11+
};
12+
13+
struct S2 {
14+
int x[3];
15+
int *px;
16+
};
17+
18+
struct S2 * test_1(int n1, int n2) {
19+
struct S1 *p1 = malloc(sizeof(struct S1)*n1 + sizeof(struct S2)*n2);
20+
struct S2 *p2 = (struct S2 *)(p1+n1); // expected-warning{{Allocation partition}}
21+
return p2;
22+
}
23+
24+
double buf[100] __attribute__((aligned(_Alignof(void*))));
25+
struct S2 * test_2(int n1) {
26+
struct S1 *p1 = (struct S1 *)buf; // ?
27+
struct S2 *p2 = (struct S2 *)(p1+n1); // expected-warning{{Allocation partition}}
28+
return p2;
29+
}
30+
31+
int * test_3(int n1, int n2) {
32+
void *p1 = malloc(sizeof(struct S1)*n1 + sizeof(struct S2)*n2);
33+
struct S2 *p2 = (struct S2 *)(p1+n1);
34+
int *p3 = (int*)(p2 + n2); // expected-warning{{Allocation partition}}
35+
return p3;
36+
}
37+
38+
void array(int i, int j) {
39+
int a[100][200];
40+
int (*p1)[200] = &a[i];
41+
int *p2 = p1[j]; // no warn
42+
*p2 = 42;
43+
}

0 commit comments

Comments
 (0)