@@ -31,17 +31,135 @@ using namespace ento;
3131
3232namespace {
3333class SubObjectRepresentabilityChecker
34- : public Checker<check::ASTDecl<RecordDecl>> {
34+ : public Checker<check::ASTDecl<RecordDecl>, check::ASTCodeBody > {
3535 BugType BT_1{this , " Field with imprecise subobject bounds" ,
3636 " CHERI portability" };
37+ BugType BT_2{this , " Address taken of a field with imprecise subobject bounds" ,
38+ " CHERI portability" };
3739
3840public:
3941 void checkASTDecl (const RecordDecl *R, AnalysisManager &mgr,
4042 BugReporter &BR) const ;
43+ void checkASTCodeBody (const Decl *D, AnalysisManager &mgr,
44+ BugReporter &BR) const ;
4145};
4246
4347} // namespace
4448
49+ namespace {
50+
51+ std::unique_ptr<BasicBugReport>
52+ reportExposedFields (const FieldDecl *D, ASTContext &ASTCtx, BugReporter &BR,
53+ uint64_t Base, uint64_t Top,
54+ std::unique_ptr<BasicBugReport> Report) {
55+ const RecordDecl *Parent = D->getParent ();
56+ auto FI = Parent->field_begin ();
57+ while (FI != Parent->field_end () &&
58+ ASTCtx.getFieldOffset (*FI) / 8 +
59+ ASTCtx.getTypeSize (FI->getType ()) / 8 <=
60+ Base)
61+ FI++;
62+
63+ bool Before = true ;
64+ while (FI != Parent->field_end () && ASTCtx.getFieldOffset (*FI) / 8 < Top) {
65+ if (*FI != D) {
66+ SmallString<1024 > Note;
67+ llvm::raw_svector_ostream OS2 (Note);
68+
69+ uint64_t CurFieldOffset = ASTCtx.getFieldOffset (*FI) / 8 ;
70+ uint64_t CurFieldSize = ASTCtx.getTypeSize (FI->getType ()) / 8 ;
71+ uint64_t BytesExposed =
72+ Before ? std::min (CurFieldSize, CurFieldOffset + CurFieldSize - Base)
73+ : std::min (CurFieldSize, Top - CurFieldOffset);
74+ OS2 << BytesExposed << " /" << CurFieldSize << " bytes exposed" ;
75+ if (cheri::hasCapability (FI->getType (), ASTCtx))
76+ OS2 << " (may expose capability!)" ;
77+
78+ PathDiagnosticLocation LN =
79+ PathDiagnosticLocation::createBegin (*FI, BR.getSourceManager ());
80+ Report->addNote (OS2.str (), LN);
81+ } else
82+ Before = false ;
83+ FI++;
84+ }
85+ return Report;
86+ }
87+
88+ #if 0
89+ // FIXME: CC_MORELLO is not set in cheri_compressed_cap
90+ // FIXME: other targets
91+ using Handler = CompressedCap128;
92+ Handler::cap_t getBoundedCap(uint64_t ParentSize, uint64_t Offset,
93+ uint64_t Size) {
94+ Handler::addr_t InitLength = Handler ::representable_length(ParentSize);
95+ Handler::cap_t MockCap = Handler::make_max_perms_cap(0, 0, InitLength);
96+ bool exact = Handler::setbounds(&MockCap, Offset, Offset + Size);
97+ assert(!exact);
98+ return MockCap;
99+ }
100+ #endif
101+
102+ } // namespace
103+
104+ std::unique_ptr<BugReport> checkField (const FieldDecl *D, AnalysisManager &mgr,
105+ BugReporter &BR, const BugType &BT) {
106+ QualType T = D->getType ();
107+
108+ ASTContext &ASTCtx = BR.getContext ();
109+ uint64_t Offset = ASTCtx.getFieldOffset (D) / 8 ;
110+ if (Offset > 0 ) {
111+ uint64_t Size = ASTCtx.getTypeSize (T) / 8 ;
112+ uint64_t ReqAlign = llvm::CompressedCapability::GetRequiredAlignment (
113+ Size, llvm::CompressedCapability::Cheri128)
114+ .value ();
115+ uint64_t CurAlign = 1 << llvm::countr_zero (Offset);
116+ if (CurAlign < ReqAlign) {
117+ /* Emit warning */
118+ SmallString<1024 > Err;
119+ llvm::raw_svector_ostream OS (Err);
120+ const PrintingPolicy &PP = ASTCtx.getPrintingPolicy ();
121+ OS << " Field '" ;
122+ D->getNameForDiagnostic (OS, PP, false );
123+ OS << " ' of type '" << T.getAsString (PP) << " '" ;
124+ OS << " (size " << Size << " )" ;
125+ OS << " requires " << ReqAlign << " byte alignment for precise bounds;" ;
126+ OS << " field offset is " << Offset;
127+ OS << " (aligned to " << CurAlign << " );" ;
128+
129+ /*
130+ * Print current bounds
131+ * TODO: use cheri_compressed_cap correctly
132+ *
133+ const RecordDecl *Parent = D->getParent();
134+ uint64_t ParentSize = ASTCtx.getTypeSize(Parent->getTypeForDecl()) / 8;
135+ auto MockCap = getBoundedCap(ParentSize, Offset, Size);
136+ uint64_t Base = MockCap.base();
137+ uint64_t Top = MockCap.top();
138+ OS << " Current bounds: " << Base << "-" << Top;
139+ */
140+
141+ // Note that this will fire for every translation unit that uses this
142+ // class. This is suboptimal, but at least scan-build will merge
143+ // duplicate HTML reports.
144+ PathDiagnosticLocation L =
145+ PathDiagnosticLocation::createBegin (D, BR.getSourceManager ());
146+ auto Report = std::make_unique<BasicBugReport>(BT, OS.str (), L);
147+ Report->setDeclWithIssue (D);
148+ Report->addRange (D->getSourceRange ());
149+
150+ /*
151+ * Add exposed fields as notes
152+ * TODO: use cheri_compressed_cap correctly
153+ Report = reportExposedFields(D, ASTCtx, BR, Base, Top, std::move(Report));
154+ */
155+
156+ return Report;
157+ }
158+ }
159+
160+ return nullptr ;
161+ }
162+
45163void SubObjectRepresentabilityChecker::checkASTDecl (const RecordDecl *R,
46164 AnalysisManager &mgr,
47165 BugReporter &BR) const {
@@ -60,85 +178,54 @@ void SubObjectRepresentabilityChecker::checkASTDecl(const RecordDecl *R,
60178 */
61179
62180 for (FieldDecl *D : R->fields ()) {
63- QualType T = D->getType ();
64-
65- ASTContext &ASTCtx = BR.getContext ();
66- uint64_t Offset = ASTCtx.getFieldOffset (D) / 8 ;
67- if (Offset > 0 ) {
68- uint64_t Size = ASTCtx.getTypeSize (T) / 8 ;
69- uint64_t ReqAlign = llvm::CompressedCapability::GetRequiredAlignment (
70- Size, llvm::CompressedCapability::Cheri128)
71- .value ();
72- uint64_t CurAlign = 1 << llvm::countr_zero (Offset);
73- if (CurAlign < ReqAlign) {
74- /* Emit warning */
75- SmallString<1024 > Err;
76- llvm::raw_svector_ostream OS (Err);
77- const PrintingPolicy &PP = ASTCtx.getPrintingPolicy ();
78- OS << " Field '" ;
79- D->getNameForDiagnostic (OS, PP, false );
80- OS << " ' of type '" << T.getAsString (PP) << " '" ;
81- OS << " (size " << Size << " )" ;
82- OS << " requires " << ReqAlign << " byte alignment for precise bounds;" ;
83- OS << " field offset is " << Offset;
84- OS << " (aligned to " << CurAlign << " );" ;
85-
86- #if 0
87- // CHERIOT FIXME: Get rid of dependency on compressed-cap-lib
88- using Handler = CompressedCap128;
89- uint64_t ParentSize = ASTCtx.getTypeSize(R->getTypeForDecl())/8;
90- Handler::addr_t InitLength = Handler ::representable_length(ParentSize);
91- Handler::cap_t MockCap = Handler::make_max_perms_cap(0, 0, InitLength);
92- bool exact = Handler::setbounds(&MockCap, Offset, Offset + Size);
93- assert(!exact);
94- auto Base = MockCap.base();
95- Handler::addr_t Top = MockCap.top();
96- OS << " Current bounds: " << Base << "-" << Top;
97- #endif
98-
99- PathDiagnosticLocation L =
100- PathDiagnosticLocation::createBegin (D, BR.getSourceManager ());
101- auto Report = std::make_unique<BasicBugReport>(BT_1, OS.str (), L);
102- Report->setDeclWithIssue (D);
103- Report->addRange (D->getSourceRange ());
181+ auto Report = checkField (D, mgr, BR, BT_1);
182+ if (Report)
183+ BR.emitReport (std::move (Report));
184+ }
185+ }
104186
105- #if 0
106- // CHERIOT FIXME: Get rid of dependency on compressed-cap-lib
107- auto FI = R->field_begin();
108- while (FI != R->field_end() &&
109- ASTCtx.getFieldOffset(*FI)/8
110- + ASTCtx.getTypeSize(FI->getType())/8 <= Base)
111- FI++;
112-
113- bool Before = true;
114- while (FI != R->field_end() && ASTCtx.getFieldOffset(*FI)/8 < Top) {
115- if (*FI != D) {
116- SmallString<1024> Note;
117- llvm::raw_svector_ostream OS2(Note);
118-
119- uint64_t CurFieldOffset = ASTCtx.getFieldOffset(*FI)/8;
120- uint64_t CurFieldSize = ASTCtx.getTypeSize(FI->getType())/8;
121- uint64_t BytesExposed =
122- Before ? std::min(CurFieldSize,
123- CurFieldOffset + CurFieldSize - Base)
124- : std::min(CurFieldSize, Top - CurFieldOffset);
125- OS2 << BytesExposed << "/" << CurFieldSize << " bytes exposed";
126- if (cheri::hasCapability(FI->getType(), ASTCtx))
127- OS2 << " (may expose capability!)";
128-
129- PathDiagnosticLocation LN =
130- PathDiagnosticLocation::createBegin(*FI, BR.getSourceManager());
131- Report->addNote(OS2.str(), LN);
132- } else
133- Before = false;
134- FI++;
187+ void SubObjectRepresentabilityChecker::checkASTCodeBody (const Decl *D,
188+ AnalysisManager &mgr,
189+ BugReporter &BR) const {
190+ using namespace ast_matchers ;
191+
192+ auto Member = memberExpr ().bind (" member" );
193+ auto Decay =
194+ castExpr (hasCastKind (CK_ArrayToPointerDecay), has (Member)).bind (" decay" );
195+ auto Addr = unaryOperator (hasOperatorName (" &" ), has (Member)).bind (" addr" );
196+
197+ auto PointerSizeCheck = traverse (TK_AsIs, stmt (anyOf (Decay, Addr)));
198+
199+ auto Matcher = decl (forEachDescendant (PointerSizeCheck));
200+
201+ auto Matches = match (Matcher, *D, BR.getContext ());
202+ for (const auto &Match : Matches) {
203+ if (const CastExpr *CE = Match.getNodeAs <CastExpr>(" decay" )) {
204+ if (const MemberExpr *ME = Match.getNodeAs <MemberExpr>(" member" )) {
205+ ValueDecl *VD = ME->getMemberDecl ();
206+ if (FieldDecl *FD = dyn_cast<FieldDecl>(VD)) {
207+ auto Report = checkField (FD, mgr, BR, BT_2);
208+ if (Report) {
209+ PathDiagnosticLocation LN = PathDiagnosticLocation::createBegin (
210+ CE, BR.getSourceManager (), mgr.getAnalysisDeclContext (D));
211+ Report->addNote (" Array to pointer decay" , LN);
212+ BR.emitReport (std::move (Report));
213+ }
214+ }
215+ }
216+ } else if (const UnaryOperator *UO =
217+ Match.getNodeAs <UnaryOperator>(" addr" )) {
218+ if (const MemberExpr *ME = Match.getNodeAs <MemberExpr>(" member" )) {
219+ ValueDecl *VD = ME->getMemberDecl ();
220+ if (FieldDecl *FD = dyn_cast<FieldDecl>(VD)) {
221+ auto Report = checkField (FD, mgr, BR, BT_2);
222+ if (Report) {
223+ PathDiagnosticLocation LN = PathDiagnosticLocation::createBegin (
224+ UO, BR.getSourceManager (), mgr.getAnalysisDeclContext (D));
225+ Report->addNote (" Address of a field taken" , LN);
226+ BR.emitReport (std::move (Report));
227+ }
135228 }
136- #endif
137-
138- // Note that this will fire for every translation unit that uses this
139- // class. This is suboptimal, but at least scan-build will merge
140- // duplicate HTML reports.
141- BR.emitReport (std::move (Report));
142229 }
143230 }
144231 }
0 commit comments