Skip to content

Commit a072f4f

Browse files
eupharinaresistor
authored andcommitted
[CHERI_CSA] New cheri.Allocation checker
1 parent 1aad0f4 commit a072f4f

File tree

7 files changed

+293
-15
lines changed

7 files changed

+293
-15
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1837,4 +1837,8 @@ def SubObjectRepresentabilityChecker : Checker<"SubObjectRepresentability">,
18371837

18381838
let ParentPackage = CHERIAlpha in {
18391839

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

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ 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+
65+
5566
} // end of namespace: cheri
5667
} // end of namespace: ento
5768
} // end of 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
} // end of namespace: cheri
3134
} // end of namespace: ento
3235
} // end of namespace: clang

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

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

540-
namespace {
541-
542-
static void describeCast(raw_ostream &OS, const CastExpr *CE,
543-
const LangOptions &LangOpts) {
544-
OS << (dyn_cast<ImplicitCastExpr>(CE) ? "implicit" : "explicit");
545-
OS << " cast from '";
546-
CE->getSubExpr()->getType().print(OS, PrintingPolicy(LangOpts));
547-
OS << "' to '";
548-
CE->getType().print(OS, PrintingPolicy(LangOpts));
549-
OS << "'";
550-
}
551-
552-
} // namespace
553-
554540
PathDiagnosticPieceRef
555541
ProvenanceSourceChecker::InvalidCapBugVisitor::VisitNode(
556542
const ExplodedNode *N, BugReporterContext &BRC,

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)