From f94a9e985651b54ff7b10f514c0de69061b5bda4 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 14 Dec 2022 12:18:12 +0000 Subject: [PATCH 01/77] [CHERI-CSA] Allow ASTContext::getIntWidth() for reference type --- clang/lib/AST/ASTContext.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 68091cfe12c3..fa5b406ca6b1 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -10972,9 +10972,11 @@ unsigned ASTContext::getIntWidth(QualType T) const { if (Target->SupportsCapabilities()) { if (T->isPointerType() && T->getAs()->isCHERICapability()) return Target->getPointerRangeForCHERICapability(); + if (T->isReferenceType() && T->getAs()->isCHERICapability()) { + return Target->getPointerRangeForCHERICapability(); + } if (T->isIntCapType()) return Target->getPointerRangeForCHERICapability(); - assert(!T->isReferenceType() && "Should probably not be handled here"); } // For builtin types, just use the standard type sizing method return (unsigned)getTypeSize(T); From fb1708e8ebb4e796d992891218f058061f7ec8ac Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 13 Jun 2024 13:30:20 +0100 Subject: [PATCH 02/77] [CHERI-CSA] Use type IntWidth instead of TypeSize for NULL ptr SVal --- .../clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h index 59bfbe3dea77..77629fe0bf93 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/BasicValueFactory.h @@ -223,7 +223,7 @@ class BasicValueFactory { const llvm::APSInt &getZeroWithTypeSize(QualType T) { assert(T->isScalarType()); - return getValue(0, Ctx.getTypeSize(T), true); + return getValue(0, Ctx.getIntWidth(T), true); } const llvm::APSInt &getTruthValue(bool b, QualType T) { From 89216c984d17d322707c6b574a57a28a250b22d4 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 21 Dec 2022 15:33:38 +0000 Subject: [PATCH 03/77] [CHERI-CSA] Improve LocAsInt arithmetic support --- .../StaticAnalyzer/Core/SimpleSValBuilder.cpp | 66 ++++++++++++++----- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index 762ecc18ecea..56a487120d8d 100644 --- a/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -403,6 +403,19 @@ static Optional tryRearrange(ProgramStateRef State, return doRearrangeUnchecked(State, Op, LSym, LInt, RSym, RInt); } +static SVal evalAdditiveIntptrOp(ProgramStateRef State, + BinaryOperator::Opcode op, Loc LHS, + NonLoc RHS, QualType LocTy, + QualType ResultTy, SimpleSValBuilder &SVB) { + ASTContext &Context = SVB.getContext(); + const CanQualType &CharPtrTy = Context.getPointerType(Context.CharTy); + + const SVal &RawPtr = SVB.evalCast(LHS, CharPtrTy, LocTy); + const Loc &RawPtrLoc = RawPtr.castAs(); + const SVal &NewLoc = SVB.evalBinOpLN(State, op, RawPtrLoc, RHS, CharPtrTy); + return SVB.evalCast(NewLoc, ResultTy, CharPtrTy); +} + SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, BinaryOperator::Opcode op, NonLoc lhs, NonLoc rhs, @@ -475,24 +488,43 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state, resultTy); case nonloc::ConcreteIntKind: { // FIXME: at the moment the implementation - // of modeling "pointers as integers" is not complete. - if (!BinaryOperator::isComparisonOp(op)) - return UnknownVal(); - // Transform the integer into a location and compare. - // FIXME: This only makes sense for comparisons. If we want to, say, - // add 1 to a LocAsInteger, we'd better unpack the Loc and add to it, - // then pack it back into a LocAsInteger. + // of modeling "pointers as integers" is not complete. llvm::APSInt i = rhs.castAs().getValue(); - // If the region has a symbolic base, pay attention to the type; it - // might be coming from a non-default address space. For non-symbolic - // regions it doesn't matter that much because such comparisons would - // most likely evaluate to concrete false anyway. FIXME: We might - // still need to handle the non-comparison case. - if (SymbolRef lSym = lhs.getAsLocSymbol(true)) - BasicVals.getAPSIntType(lSym->getType()).apply(i); - else - BasicVals.getAPSIntType(Context.VoidPtrTy).apply(i); - return evalBinOpLL(state, op, lhsL, makeLoc(i), resultTy); + + // FIXME: If the region has a symbolic base, pay attention to the + // type; it might be coming from a non-default address space. + // For comparisons with non-symbolic regions it doesn't matter that + // much because such comparisons would most likely evaluate to + // concrete false anyway. + if (BinaryOperator::isComparisonOp(op)) { + // Transform the integer into a location and compare. + SymbolRef LSym = lhsL.getAsLocSymbol(true); + const QualType &symType = LSym ? LSym->getType() : Context.VoidPtrTy; + BasicVals.getAPSIntType(symType).apply(i); + return evalBinOpLL(state, op, lhsL, makeLoc(i), resultTy); + } + + // If we want to, say, add 1 to a LocAsInteger, we'd better unpack + // the Loc and add to it, then pack it back into a LocAsInteger. + if (BinaryOperator::isAdditiveOp(op)) + if (SymbolRef lSym = lhsL.getAsLocSymbol(false)) + return evalAdditiveIntptrOp(state, op, lhsL, rhs, lSym->getType(), + resultTy, *this); + + return UnknownVal(); + } + case nonloc::SymbolValKind: { + if (BinaryOperator::isEqualityOp(op)) + return makeTruthVal(op != BO_EQ, resultTy); + + if (BinaryOperator::isAdditiveOp(op)) { + if (SymbolRef lSym = lhsL.getAsLocSymbol(false)) + return evalAdditiveIntptrOp(state, op, lhsL, rhs, lSym->getType(), + resultTy, *this); + } + + // This case also handles pointer arithmetic. + return makeSymExprValNN(op, InputLHS, InputRHS, resultTy); } default: switch (op) { From 90667685d5d3d6b7e26faf093a6d888518627b7d Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 22 Dec 2022 15:03:05 +0000 Subject: [PATCH 04/77] [CHERI-CSA] Add provenance bit to LocAsInteger --- .../Core/PathSensitive/SValBuilder.h | 5 ++++- .../StaticAnalyzer/Core/PathSensitive/SVals.h | 8 ++++++- clang/lib/StaticAnalyzer/Core/SValBuilder.cpp | 22 ++++++++++++++----- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h index 1b9526324086..d0deb6d419d1 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h @@ -305,7 +305,10 @@ class SValBuilder { return nonloc::ConcreteInt(BasicVals.getValue(integer, ptrType)); } - NonLoc makeLocAsInteger(Loc loc, unsigned bits) { + NonLoc makeLocAsInteger(Loc loc, unsigned bits, bool hasProvenance) { + assert((bits & ~255) == 0); + if (hasProvenance) + bits |= 256; return nonloc::LocAsInteger(BasicVals.getPersistentSValWithData(loc, bits)); } diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h index 2ae811ee3365..b9066ce6866a 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h @@ -364,7 +364,13 @@ class LocAsInteger : public NonLoc { unsigned getNumBits() const { const std::pair *D = static_cast *>(Data); - return D->second; + return D->second & 255; + } + + bool hasProvenance() const { + const std::pair *D = + static_cast *>(Data); + return D->second & 256; } static bool classof(SVal V) { diff --git a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp index d90e869196eb..0bc1aca24354 100644 --- a/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/clang/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -628,11 +628,13 @@ class EvalCastVisitor : public SValVisitor { SValBuilder &VB; ASTContext &Context; QualType CastTy, OriginalTy; + bool SrcHasProvenance; public: - EvalCastVisitor(SValBuilder &VB, QualType CastTy, QualType OriginalTy) + EvalCastVisitor(SValBuilder &VB, QualType CastTy, QualType OriginalTy, + bool SrcProv) : VB(VB), Context(VB.getContext()), CastTy(CastTy), - OriginalTy(OriginalTy) {} + OriginalTy(OriginalTy), SrcHasProvenance(SrcProv) {} SVal Visit(SVal V) { if (CastTy.isNull()) @@ -690,7 +692,7 @@ class EvalCastVisitor : public SValVisitor { // Pointer to integer. if (CastTy->isIntegralOrEnumerationType()) { const unsigned BitWidth = Context.getIntWidth(CastTy); - return VB.makeLocAsInteger(V, BitWidth); + return VB.makeLocAsInteger(V, BitWidth, CastTy->isIntCapType()); } const bool IsUnknownOriginalType = OriginalTy.isNull(); @@ -757,7 +759,8 @@ class EvalCastVisitor : public SValVisitor { // QualType pointerTy = C.getPointerType(elemTy); } const unsigned BitWidth = Context.getIntWidth(CastTy); - return VB.makeLocAsInteger(Val.castAs(), BitWidth); + bool HasProvenance = this->SrcHasProvenance && CastTy->isIntCapType(); + return VB.makeLocAsInteger(Val.castAs(), BitWidth, HasProvenance); } // Pointer to pointer. @@ -937,7 +940,7 @@ class EvalCastVisitor : public SValVisitor { if (CastSize == V.getNumBits()) return V; - return VB.makeLocAsInteger(L, CastSize); + return VB.makeLocAsInteger(L, CastSize, this->SrcHasProvenance); } } @@ -1090,6 +1093,13 @@ class EvalCastVisitor : public SValVisitor { /// FIXME: If `OriginalTy.isNull()` is true, then cast performs based on CastTy /// only. This behavior is uncertain and should be improved. SVal SValBuilder::evalCast(SVal V, QualType CastTy, QualType OriginalTy) { - EvalCastVisitor TRV{*this, CastTy, OriginalTy}; + bool SrcHasProvenance = false; + if (V.getAs()) + SrcHasProvenance = true; + if (auto LaI = V.getAs()) { + SrcHasProvenance = LaI.getValue().hasProvenance(); + } + + EvalCastVisitor TRV{*this, CastTy, OriginalTy, SrcHasProvenance}; return TRV.Visit(V); } From 5af293be387123206151c05f503f797bf73bb447 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 9 Jan 2023 12:28:21 +0000 Subject: [PATCH 05/77] [analyzer] scan-build: Retain -nostdinc++ option --- clang/tools/scan-build/libexec/ccc-analyzer | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/tools/scan-build/libexec/ccc-analyzer b/clang/tools/scan-build/libexec/ccc-analyzer index 35b7a27126c5..5ae9baf9a427 100755 --- a/clang/tools/scan-build/libexec/ccc-analyzer +++ b/clang/tools/scan-build/libexec/ccc-analyzer @@ -355,6 +355,7 @@ sub Analyze { my %CompileOptionMap = ( '-nostdinc' => 0, + '-nostdinc++' => 0, '-include' => 1, '-idirafter' => 1, '-imacros' => 1, From 44ffe2363feb3ef734da0222fe0e64543a2d1617 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 21 Dec 2022 15:32:53 +0000 Subject: [PATCH 06/77] [CHERI-CSA] Add alpha.cheri.ProvenanceSourceChecker --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 19 + .../CHERI/ProvenanceSourceChecker.cpp | 499 ++++++++++++++++++ .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../Checkers/CHERI/provenance-source.c | 181 +++++++ 4 files changed, 700 insertions(+) create mode 100644 clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp create mode 100644 clang/test/Analysis/Checkers/CHERI/provenance-source.c diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index cf8cec3b13c3..1274d6f06157 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -120,6 +120,9 @@ def FuchsiaAlpha : Package<"fuchsia">, ParentPackage; def WebKit : Package<"webkit">; def WebKitAlpha : Package<"webkit">, ParentPackage; +def CHERI : Package<"cheri">; +def CHERIAlpha : Package<"cheri">, ParentPackage; + //===----------------------------------------------------------------------===// // Core Checkers. //===----------------------------------------------------------------------===// @@ -1734,3 +1737,19 @@ def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">, Documentation; } // end alpha.webkit + +//===----------------------------------------------------------------------===// +// CHERI checkers. +//===----------------------------------------------------------------------===// + +let ParentPackage = CHERI in { + +} // end cheri + +let ParentPackage = CHERIAlpha in { + +def ProvenanceSourceChecker : Checker<"ProvenanceSourceChecker">, + HelpText<"Check expressions with ambiguous provenance source.">, + Documentation; + +} // end alpha.cheri diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp new file mode 100644 index 000000000000..69fdcc745b31 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -0,0 +1,499 @@ +//===-- ProvenanceSourceChecker.cpp - Provenance Source Checker -*- 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 checker that detects binary arithmetic expressions with +// capability type operands where provenance source is ambiguous. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { +class ProvenanceSourceChecker : public Checker, + check::PreStmt, + check::PostStmt, + check::DeadSymbols> { + std::unique_ptr AmbiguousProvenanceBinOpBugType; + std::unique_ptr AmbiguousProvenancePtrBugType; + std::unique_ptr NullDerivedCapPtrBugType; + +private: + class NullDerivedCapBugVisitor : public BugReporterVisitor { + public: + NullDerivedCapBugVisitor(SymbolRef SR) : Sym(SR) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Sym); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; + private: + SymbolRef Sym; + }; + + class Ptr2IntBugVisitor : public BugReporterVisitor { + public: + Ptr2IntBugVisitor(const MemRegion *R) : Reg(R) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Reg); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; + private: + const MemRegion *Reg; + }; + +public: + ProvenanceSourceChecker(); + + void checkPostStmt(const CastExpr *CE, CheckerContext &C) const; + void checkPreStmt(const CastExpr *CE, CheckerContext &C) const; + void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + +private: + // Utility + ExplodedNode *emitAmbiguousProvenanceWarn(const BinaryOperator *BO, + CheckerContext &C, bool LHSIsAddr, + bool LHSIsNullDerived, + bool RHSIsAddr, + bool RHSIsNullDerived) const; + + static void propagateProvenanceInfoForBinOp(ExplodedNode *N, + const BinaryOperator *BO, + CheckerContext &C, + bool NullDerivedCap); +}; +} // namespace + +REGISTER_SET_WITH_PROGRAMSTATE(NullDerivedCap, SymbolRef) +REGISTER_SET_WITH_PROGRAMSTATE(AmbiguousProvenanceSym, SymbolRef) +REGISTER_SET_WITH_PROGRAMSTATE(AmbiguousProvenanceReg, const MemRegion *) +REGISTER_TRAIT_WITH_PROGRAMSTATE(Ptr2IntCapId, unsigned) + +ProvenanceSourceChecker::ProvenanceSourceChecker() { + AmbiguousProvenanceBinOpBugType.reset( + new BugType(this, + "Binary operation with ambiguous provenance", + "CHERI portability")); + AmbiguousProvenancePtrBugType.reset( + new BugType(this, + "Capability with ambiguous provenance used as pointer", + "CHERI portability")); + NullDerivedCapPtrBugType.reset( + new BugType(this, + "NULL-derived capability used as pointer", + "CHERI portability")); +} + +static bool isIntegerToIntCapCast(const CastExpr *CE) { + if (CE->getCastKind() != CK_IntegralCast) + return false; + if (!CE->getType()->isIntCapType() || + CE->getSubExpr()->getType()->isIntCapType()) + return false; + return true; +} + +static bool isPointerToIntCapCast(const CastExpr *CE) { + if (CE->getCastKind() != clang::CK_PointerToIntegral) + return false; + if (!CE->getType()->isIntCapType()) + return false; + return true; +} + +void ProvenanceSourceChecker::checkPostStmt(const CastExpr *CE, + CheckerContext &C) const { + if (isIntegerToIntCapCast(CE)) { + SymbolRef IntCapSym = C.getSVal(CE).getAsSymbol(); + if (!IntCapSym) + return; + + if (C.getSVal(CE).getAsLocSymbol()) + return; // skip locAsInteger + + ProgramStateRef State = C.getState(); + State = State->add(IntCapSym); + C.addTransition(State); + } else if (isPointerToIntCapCast(CE)) { + // Prev node may be reclaimed as "uninteresting", in this case we will not + // be able to create path diagnostic for it; therefore we modify the state, + // i.e. create the node that definitely will not be deleted + ProgramStateRef State = C.getState(); + unsigned PrevId = State->get(); + C.addTransition(State->set(PrevId + 1)); + } +} + +static bool hasAmbiguousProvenance(ProgramStateRef State, const SVal &Val) { + if (SymbolRef Sym = Val.getAsSymbol()) + return State->contains(Sym); + + if (const MemRegion *Reg = Val.getAsRegion()) + return State->contains(Reg); + + return false; +} + +static bool hasNoProvenance(ProgramStateRef State, const SVal &Val) { + if (Val.isConstant()) + return true; + + if (const auto &LocAsInt = Val.getAs()) + return !LocAsInt->hasProvenance(); + + SymbolRef Sym = Val.getAsSymbol(); + if (Sym && State->contains(Sym)) + return true; + return false; +} + +static bool isAddress(const SVal &Val) { + if (!Val.getAsLocSymbol(true)) + return false; + + if (const auto &LocAsInt = Val.getAs()) + return LocAsInt->hasProvenance(); + + return true; +} + +static bool isIntToVoidPtrCast(const CastExpr *CE) { + if (!CE->getType()->isVoidPointerType()) + return false; + + const Expr *Src = CE->getSubExpr(); + if (!Src->getType()->isIntCapType()) + return false; + + if (auto *CE2 = dyn_cast(Src)) { + const QualType &T = CE2->getSubExpr()->getType(); + return T->isIntegerType() && !T->isIntCapType(); + } + + return false; +} + +// Report intcap with ambiguous or NULL-derived provenance cast to pointer +void ProvenanceSourceChecker::checkPreStmt(const CastExpr *CE, + CheckerContext &C) const { + if (CE->getCastKind() != clang::CK_IntegralToPointer) + return; + + ProgramStateRef const State = C.getState(); + const SVal &SrcVal = C.getSVal(CE->getSubExpr()); + + std::unique_ptr R; + if (hasAmbiguousProvenance(State, SrcVal)) { + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); + if (!ErrNode) + return; + R = std::make_unique( + *AmbiguousProvenancePtrBugType, + "Capability with ambiguous provenance is used as pointer", ErrNode); + } else if (hasNoProvenance(State, SrcVal) && !SrcVal.isConstant() + && !isIntToVoidPtrCast(CE)) { + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); + if (!ErrNode) + return; + R = std::make_unique( + *NullDerivedCapPtrBugType, "NULL-derived capability is used as pointer", + ErrNode); + if (SymbolRef S = SrcVal.getAsSymbol()) + R->addVisitor(std::make_unique(S)); + } else + return; + + R->markInteresting(SrcVal); + R->addRange(CE->getSourceRange()); + C.emitReport(std::move(R)); +} + +static bool justConverted2IntCap(Expr *E, const ASTContext &Ctx) { + assert(E->getType()->isCHERICapabilityType(Ctx, true)); + if (auto *CE = dyn_cast(E)) { + const QualType OrigType = CE->getSubExpr()->getType(); + if (!OrigType->isCHERICapabilityType(Ctx, true)) + return true; + } + return false; +} + +ExplodedNode *ProvenanceSourceChecker::emitAmbiguousProvenanceWarn( + const BinaryOperator *BO, CheckerContext &C, + bool LHSIsAddr, bool LHSIsNullDerived, + bool RHSIsAddr, bool RHSIsNullDerived) const { + SmallString<350> ErrorMessage; + llvm::raw_svector_ostream OS(ErrorMessage); + + const QualType &T = BO->getType(); + bool const IsUnsigned = T->isUnsignedIntegerType(); + + OS << "Result of '"<< BO->getOpcodeStr() << "' on capability type '"; + T.print(OS, PrintingPolicy(C.getASTContext().getLangOpts())); + OS << "'; it is unclear which side should be used as the source " + "of provenance; consider indicating the provenance-carrying argument " + "explicitly by casting the other argument to '" + << (IsUnsigned ? "size_t" : "ptrdiff_t") << "'. "; + + OS << "Note: along this path, "; + if (LHSIsAddr && RHSIsAddr) + OS << "LHS and RHS were derived from pointers"; + else if (LHSIsNullDerived && RHSIsNullDerived) { + OS << "LHS and RHS were derived from NULL"; + } else { + if (LHSIsAddr) + OS << "LHS was derived from pointer"; + if (LHSIsNullDerived) + OS << "LHS was derived from NULL"; + if ((LHSIsAddr || LHSIsNullDerived) && (RHSIsAddr || RHSIsNullDerived)) + OS << ", "; + if (RHSIsAddr) + OS << "RHS was derived from pointer"; + if (RHSIsNullDerived) + OS << "RHS was derived from NULL"; + } + + // Generate the report. + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); + if (!ErrNode) + return nullptr; + auto R = std::make_unique( + *AmbiguousProvenanceBinOpBugType, ErrorMessage, ErrNode); + R->addRange(BO->getSourceRange()); + + const SVal &LHSVal = C.getSVal(BO->getLHS()); + R->markInteresting(LHSVal); + if (SymbolRef LS = LHSVal.getAsSymbol()) + R->addVisitor(std::make_unique(LS)); + if (const MemRegion *Reg = LHSVal.getAsRegion()) + R->addVisitor(std::make_unique(Reg)); + + const SVal &RHSVal = C.getSVal(BO->getRHS()); + R->markInteresting(RHSVal); + if (SymbolRef RS = RHSVal.getAsSymbol()) + R->addVisitor(std::make_unique(RS)); + if (const MemRegion *Reg = RHSVal.getAsRegion()) + R->addVisitor(std::make_unique(Reg)); + + C.emitReport(std::move(R)); + return ErrNode; +} + +void ProvenanceSourceChecker::propagateProvenanceInfoForBinOp( + ExplodedNode *N, const BinaryOperator *BO, CheckerContext &C, + bool NullDerivedRes) { + + ProgramStateRef State = N->getState(); + SVal ResVal = C.getSVal(BO); + if (ResVal.isUnknown()) { + const LocationContext *LCtx = C.getLocationContext(); + ResVal = C.getSValBuilder().conjureSymbolVal( + nullptr, BO, LCtx, BO->getType(), C.blockCount()); + State = State->BindExpr(BO, LCtx, ResVal); + } + + if (SymbolRef ResSym = ResVal.getAsSymbol()) + State = NullDerivedRes ? State->add(ResSym) + : State->add(ResSym); + else if (const MemRegion *Reg = ResVal.getAsRegion()) + State = State->add(Reg); + else + return; // no result to propagate to + + const NoteTag *BinOpTag = + NullDerivedRes ? nullptr // note will be added in BugVisitor + : C.getNoteTag("Binary operator has ambiguous provenance"); + + C.addTransition(State, N, BinOpTag); +} + +// Report intcap binary expressions with ambiguous provenance, +// store them to report again if used as pointer +void ProvenanceSourceChecker::checkPostStmt(const BinaryOperator *BO, + CheckerContext &C) const { + BinaryOperatorKind const OpCode = BO->getOpcode(); + if (!(BinaryOperator::isAdditiveOp(OpCode) + || BinaryOperator::isMultiplicativeOp(OpCode) + || BinaryOperator::isBitwiseOp(OpCode))) + return; + bool const IsSub = OpCode == clang::BO_Sub || OpCode == clang::BO_SubAssign; + + Expr *LHS = BO->getLHS(); + Expr *RHS = BO->getRHS(); + if (!LHS->getType()->isIntCapType() || !RHS->getType()->isIntCapType()) + return; + + ProgramStateRef const State = C.getState(); + + const SVal &LHSVal = C.getSVal(LHS); + const SVal &RHSVal = C.getSVal(RHS); + + bool const LHSIsAddr = isAddress(LHSVal); + bool const LHSIsNullDerived = !LHSIsAddr && hasNoProvenance(State, LHSVal); + bool const RHSIsAddr = isAddress(RHSVal); + bool const RHSIsNullDerived = !RHSIsAddr && hasNoProvenance(State, RHSVal); + if (!LHSIsAddr && !LHSIsNullDerived && !RHSIsAddr && !RHSIsNullDerived) + return; + + // Operands that were converted to intcap for this binaryOp are not used to + // derive the provenance of the result + // FIXME: explicit attr::CHERINoProvenance + bool const LHSActiveProv = !justConverted2IntCap(LHS, C.getASTContext()); + bool const RHSActiveProv = !justConverted2IntCap(RHS, C.getASTContext()); + + ExplodedNode *N; + bool const NullDerivedRes = LHSIsNullDerived && RHSIsNullDerived; + if (LHSActiveProv && RHSActiveProv && !IsSub) { + N = emitAmbiguousProvenanceWarn(BO, C, LHSIsAddr, LHSIsNullDerived, + RHSIsAddr, RHSIsNullDerived); + if (!N) + N = C.getPredecessor(); + } else if (NullDerivedRes) { + N = C.getPredecessor(); + } else + return; + + // Propagate info for result + propagateProvenanceInfoForBinOp(N, BO, C, NullDerivedRes); +} + +void ProvenanceSourceChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + bool Removed = false; + const NullDerivedCapTy &Set = State->get(); + for (const auto &Sym : Set) { + if (!SymReaper.isDead(Sym)) + continue; + State = State->remove(Sym); + Removed = true; + } + + const AmbiguousProvenanceSymTy &Set2 = State->get(); + for (const auto &Sym : Set2) { + if (!SymReaper.isDead(Sym)) + continue; + State = State->remove(Sym); + Removed = true; + } + + if (Removed) + C.addTransition(State); +} + +static void describeCast(raw_ostream &OS, const CastExpr *CE, + const LangOptions &LangOpts) { + OS << (dyn_cast(CE) ? "implicit" : "explicit"); + OS << " cast from '"; + CE->getSubExpr()->getType().print(OS, PrintingPolicy(LangOpts)); + OS << "' to '"; + CE->getType().print(OS, PrintingPolicy(LangOpts)); + OS << "'"; +} + +PathDiagnosticPieceRef +ProvenanceSourceChecker::NullDerivedCapBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { + + const Stmt *S = N->getStmtForDiagnostics(); + if (!S) + return nullptr; + + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + + if (const CastExpr *CE = dyn_cast(S)) { + if (!isIntegerToIntCapCast(CE)) + return nullptr; + + if (Sym != N->getSVal(CE).getAsSymbol()) + return nullptr; + + if (!N->getState()->contains(Sym)) + return nullptr; + + OS << "NULL-derived capability: "; + describeCast(OS, CE, BRC.getASTContext().getLangOpts()); + } else if (const BinaryOperator *BO = dyn_cast(S)) { + BinaryOperatorKind const OpCode = BO->getOpcode(); + if (!(BinaryOperator::isAdditiveOp(OpCode) + || BinaryOperator::isMultiplicativeOp(OpCode) + || BinaryOperator::isBitwiseOp(OpCode))) + return nullptr; + + if (!BO->getType()->isIntCapType() || Sym != N->getSVal(BO).getAsSymbol()) + return nullptr; + + if (!N->getState()->contains(Sym)) + return nullptr; + + OS << "Result of '" << BO->getOpcodeStr() + << "' is a NULL-derived capability"; + } else + return nullptr; + + // Generate the extra diagnostic. + PathDiagnosticLocation const Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return std::make_shared(Pos, OS.str(), true); +} + +PathDiagnosticPieceRef +ProvenanceSourceChecker::Ptr2IntBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { + + const Stmt *S = N->getStmtForDiagnostics(); + if (!S) + return nullptr; + + const CastExpr *CE = dyn_cast(S); + if (!CE || !isPointerToIntCapCast(CE)) + return nullptr; + + if (Reg != N->getSVal(CE).getAsRegion()) + return nullptr; + + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << "Capability derived from pointer: "; + describeCast(OS, CE, BRC.getASTContext().getLangOpts()); + + // Generate the extra diagnostic. + PathDiagnosticLocation const Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return std::make_shared(Pos, OS.str(), true); +} + +void ento::registerProvenanceSourceChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterProvenanceSourceChecker(const CheckerManager &Mgr) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 84886b93d2e4..d7aeed81fc21 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -24,6 +24,7 @@ add_clang_library(clangStaticAnalyzerCheckers CheckSecuritySyntaxOnly.cpp CheckSizeofPointer.cpp CheckerDocumentation.cpp + CHERI/ProvenanceSourceChecker.cpp ChrootChecker.cpp CloneChecker.cpp ContainerModeling.cpp diff --git a/clang/test/Analysis/Checkers/CHERI/provenance-source.c b/clang/test/Analysis/Checkers/CHERI/provenance-source.c new file mode 100644 index 000000000000..7bcb77cec688 --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/provenance-source.c @@ -0,0 +1,181 @@ +// RUN: %cheri_purecap_cc1 -analyze -analyzer-checker=core,alpha.cheri.ProvenanceSourceChecker -verify %s + +typedef __intcap_t intptr_t; +typedef __uintcap_t uintptr_t; +typedef long int ptrdiff_t; + +char left_prov(int d, char *p) { + intptr_t a = d; + intptr_t b = (intptr_t)p; + + intptr_t s = b + a; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} + // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + return *(char*)s;// expected-warning{{Capability with ambiguous provenance is used as pointer}} +} + +char right_prov(unsigned d, char *p) { + uintptr_t a = d; + uintptr_t b = (uintptr_t)p; + + uintptr_t s = a + b; // expected-warning{{Result of '+' on capability type 'unsigned __intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'size_t'. Note: along this path, LHS was derived from NULL, RHS was derived from pointer}} + // expected-warning@-1{{binary expression on capability types 'uintptr_t' (aka 'unsigned __intcap') and 'uintptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + return *(char*)s;// expected-warning{{Capability with ambiguous provenance is used as pointer}} +} + +char both_prov(int* d, char *p) { + intptr_t a = (intptr_t)d; + intptr_t b = (intptr_t)p; + + intptr_t s = a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS and RHS were derived from pointers}} + // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + return *(char*)s; // expected-warning{{Capability with ambiguous provenance is used as pointer}} +} + +char no_prov(int d, char p) { + intptr_t a = (intptr_t)d; + intptr_t b = (intptr_t)p; + + intptr_t s = a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS and RHS were derived from NULL}} + // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + + return *(char*)s; // expected-warning{{NULL-derived capability is used as pointer}} +} + +char right_prov_cond(int d, char *p, int x) { + intptr_t a = d; + + intptr_t b; + if (d > 42) + b = (intptr_t)p; + else + b = x; + + intptr_t s = a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS and RHS were derived from NULL}} + // expected-warning@-1{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from NULL, RHS was derived from pointer}} + // expected-warning@-2{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + return *(char*)s;// expected-warning{{Capability with ambiguous provenance is used as pointer}} + // expected-warning@-1{{NULL-derived capability is used as pointer}} +} + +intptr_t no_bug(int *p, intptr_t *u) { + int *d = p + 20; + + intptr_t a = (intptr_t)d; + *u = a; + intptr_t b = (intptr_t)p; + + a = b; + return a; +} + +char add_const(int *p, int x) { + intptr_t a = (intptr_t)p + 42; + intptr_t b = (intptr_t)x; + + intptr_t s = a | b; // expected-warning{{Result of '|' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} + // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + return *(char*)s; // expected-warning{{Capability with ambiguous provenance is used as pointer}} +} + +char add_var(int *p, int x, int c) { + intptr_t a = (intptr_t)p + c; + intptr_t b = (intptr_t)x; + + intptr_t s = a | b; // expected-warning{{Result of '|' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} + // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + return *(char*)s; // expected-warning{{Capability with ambiguous provenance is used as pointer}} +} + +int * null_derived(int x) { + intptr_t u = (intptr_t)x; + return (int*)u; // expected-warning{{NULL-derived capability is used as pointer}} +} + +uintptr_t fn1(char *str, int f) { + str++; + intptr_t x = f; + return ((intptr_t)str & x); + // expected-warning@-1{{Result of '&' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} + // expected-warning@-2{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} +} + +int fp1(char *s1, char *s2) { + intptr_t a = (intptr_t)s1; + intptr_t b = (intptr_t)s2; + return a - b; +} + +void *fp2(char *p) { + void *a = (void*)(uintptr_t)p; + return a; +} + +intptr_t fp3(char *s1, char *s2) { + intptr_t a __attribute__((cheri_no_provenance)); + a = (intptr_t)s1; + intptr_t b = (intptr_t)s2; + return a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS and RHS were derived from pointers}} FIXME: expect no warning +} + +uintptr_t fp4(char *str, int f) { + return ((intptr_t)str & (intptr_t)(f)); +} + +uintptr_t fn2(char *a, char *b) { + uintptr_t msk = sizeof (long) - 1; + + uintptr_t x = (uintptr_t)a | (uintptr_t)b; + // expected-warning@-1{{Result of '|' on capability type 'unsigned __intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'size_t'. Note: along this path, LHS and RHS were derived from pointers}} + // expected-warning@-2{{binary expression on capability types 'uintptr_t' (aka 'unsigned __intcap') and 'uintptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + + int ma = x & msk; + // expected-warning@-1{{Result of '&' on capability type 'unsigned __intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'size_t'. Note: along this path, RHS was derived from NULL}} + // expected-warning@-2{{binary expression on capability types 'uintptr_t' (aka 'unsigned __intcap') and 'uintptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + + return ma; +} + +char *ptrdiff(char *a, unsigned x) { + intptr_t ip = ((ptrdiff_t)a | (intptr_t)x); + char *p = (char*) ip; // expected-warning{{NULL-derived capability is used as pointer}} + return p; +} + +int fp5(char *a, unsigned x) { + void *p = (void*)(uintptr_t)a; + void *q = (void*)(uintptr_t)x; // common pattern, don't fire warning + return (char*)p - (char*)q; +} + +char* const2ptr(int *p, int x) { + return (char*)(-1); +} + +//------------------- Inter-procedural warnings --------------------- + +static int *p; + +intptr_t get_ptr(void) { + return (intptr_t)p; + +} + +intptr_t foo(int d) { + intptr_t a = d; + intptr_t b = get_ptr(); + + return b + a; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} + // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} +} + +intptr_t add(intptr_t a, intptr_t b) { + return a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from NULL, RHS was derived from pointer}} + // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} +} + +intptr_t bar(int d) { + intptr_t a = d; + intptr_t b = get_ptr(); + + return add(a, b); +} From bbda96462a89c9ed069978f0dfc5fcac02c1ab8e Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 8 Feb 2023 12:30:29 +0000 Subject: [PATCH 07/77] [CHERI-CSA] ProvenanceSourceChecker: add subtraction --- .../CHERI/ProvenanceSourceChecker.cpp | 110 ++++++++++++------ .../Checkers/CHERI/provenance-source.c | 19 ++- 2 files changed, 90 insertions(+), 39 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index 69fdcc745b31..8c7abdbb8ba4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -27,12 +27,13 @@ class ProvenanceSourceChecker : public Checker, check::DeadSymbols> { std::unique_ptr AmbiguousProvenanceBinOpBugType; std::unique_ptr AmbiguousProvenancePtrBugType; - std::unique_ptr NullDerivedCapPtrBugType; + std::unique_ptr InvalidCapPtrBugType; + std::unique_ptr PtrdiffAsIntCapBugType; private: - class NullDerivedCapBugVisitor : public BugReporterVisitor { + class InvalidCapBugVisitor : public BugReporterVisitor { public: - NullDerivedCapBugVisitor(SymbolRef SR) : Sym(SR) {} + InvalidCapBugVisitor(SymbolRef SR) : Sym(SR) {} void Profile(llvm::FoldingSetNodeID &ID) const override { static int X = 0; @@ -80,30 +81,37 @@ class ProvenanceSourceChecker : public Checker, bool RHSIsAddr, bool RHSIsNullDerived) const; + ExplodedNode *emitPtrdiffAsIntCapWarn(const BinaryOperator *BO, + CheckerContext &C) const; + static void propagateProvenanceInfoForBinOp(ExplodedNode *N, const BinaryOperator *BO, CheckerContext &C, - bool NullDerivedCap); + bool IsInvalidCap); }; } // namespace -REGISTER_SET_WITH_PROGRAMSTATE(NullDerivedCap, SymbolRef) +REGISTER_SET_WITH_PROGRAMSTATE(InvalidCap, SymbolRef) REGISTER_SET_WITH_PROGRAMSTATE(AmbiguousProvenanceSym, SymbolRef) REGISTER_SET_WITH_PROGRAMSTATE(AmbiguousProvenanceReg, const MemRegion *) REGISTER_TRAIT_WITH_PROGRAMSTATE(Ptr2IntCapId, unsigned) ProvenanceSourceChecker::ProvenanceSourceChecker() { AmbiguousProvenanceBinOpBugType.reset( - new BugType(this, + new BugType(this, "Binary operation with ambiguous provenance", "CHERI portability")); AmbiguousProvenancePtrBugType.reset( new BugType(this, "Capability with ambiguous provenance used as pointer", "CHERI portability")); - NullDerivedCapPtrBugType.reset( + InvalidCapPtrBugType.reset( + new BugType(this, + "Invalid capability used as pointer", + "CHERI portability")); + PtrdiffAsIntCapBugType.reset( new BugType(this, - "NULL-derived capability used as pointer", + "Pointer difference as capability", "CHERI portability")); } @@ -135,15 +143,14 @@ void ProvenanceSourceChecker::checkPostStmt(const CastExpr *CE, return; // skip locAsInteger ProgramStateRef State = C.getState(); - State = State->add(IntCapSym); + State = State->add(IntCapSym); C.addTransition(State); } else if (isPointerToIntCapCast(CE)) { // Prev node may be reclaimed as "uninteresting", in this case we will not // be able to create path diagnostic for it; therefore we modify the state, // i.e. create the node that definitely will not be deleted - ProgramStateRef State = C.getState(); - unsigned PrevId = State->get(); - C.addTransition(State->set(PrevId + 1)); + ProgramStateRef const State = C.getState(); + C.addTransition(State->set(State->get() + 1)); } } @@ -165,7 +172,7 @@ static bool hasNoProvenance(ProgramStateRef State, const SVal &Val) { return !LocAsInt->hasProvenance(); SymbolRef Sym = Val.getAsSymbol(); - if (Sym && State->contains(Sym)) + if (Sym && State->contains(Sym)) return true; return false; } @@ -219,10 +226,10 @@ void ProvenanceSourceChecker::checkPreStmt(const CastExpr *CE, if (!ErrNode) return; R = std::make_unique( - *NullDerivedCapPtrBugType, "NULL-derived capability is used as pointer", + *InvalidCapPtrBugType, "Invalid capability is used as pointer", ErrNode); if (SymbolRef S = SrcVal.getAsSymbol()) - R->addVisitor(std::make_unique(S)); + R->addVisitor(std::make_unique(S)); } else return; @@ -241,6 +248,31 @@ static bool justConverted2IntCap(Expr *E, const ASTContext &Ctx) { return false; } +ExplodedNode *ProvenanceSourceChecker::emitPtrdiffAsIntCapWarn( + const BinaryOperator *BO, CheckerContext &C) const { + // Generate the report. + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); + if (!ErrNode) + return nullptr; + auto R = std::make_unique( + *PtrdiffAsIntCapBugType, "Pointer difference as capability", + ErrNode); + R->addRange(BO->getSourceRange()); + + const SVal &LHSVal = C.getSVal(BO->getLHS()); + R->markInteresting(LHSVal); + if (const MemRegion *Reg = LHSVal.getAsRegion()) + R->addVisitor(std::make_unique(Reg)); + + const SVal &RHSVal = C.getSVal(BO->getRHS()); + R->markInteresting(RHSVal); + if (const MemRegion *Reg = RHSVal.getAsRegion()) + R->addVisitor(std::make_unique(Reg)); + + C.emitReport(std::move(R)); + return ErrNode; +} + ExplodedNode *ProvenanceSourceChecker::emitAmbiguousProvenanceWarn( const BinaryOperator *BO, CheckerContext &C, bool LHSIsAddr, bool LHSIsNullDerived, @@ -287,14 +319,14 @@ ExplodedNode *ProvenanceSourceChecker::emitAmbiguousProvenanceWarn( const SVal &LHSVal = C.getSVal(BO->getLHS()); R->markInteresting(LHSVal); if (SymbolRef LS = LHSVal.getAsSymbol()) - R->addVisitor(std::make_unique(LS)); + R->addVisitor(std::make_unique(LS)); if (const MemRegion *Reg = LHSVal.getAsRegion()) R->addVisitor(std::make_unique(Reg)); const SVal &RHSVal = C.getSVal(BO->getRHS()); R->markInteresting(RHSVal); if (SymbolRef RS = RHSVal.getAsSymbol()) - R->addVisitor(std::make_unique(RS)); + R->addVisitor(std::make_unique(RS)); if (const MemRegion *Reg = RHSVal.getAsRegion()) R->addVisitor(std::make_unique(Reg)); @@ -304,7 +336,7 @@ ExplodedNode *ProvenanceSourceChecker::emitAmbiguousProvenanceWarn( void ProvenanceSourceChecker::propagateProvenanceInfoForBinOp( ExplodedNode *N, const BinaryOperator *BO, CheckerContext &C, - bool NullDerivedRes) { + bool IsInvalidCap) { ProgramStateRef State = N->getState(); SVal ResVal = C.getSVal(BO); @@ -316,16 +348,16 @@ void ProvenanceSourceChecker::propagateProvenanceInfoForBinOp( } if (SymbolRef ResSym = ResVal.getAsSymbol()) - State = NullDerivedRes ? State->add(ResSym) - : State->add(ResSym); + State = IsInvalidCap ? State->add(ResSym) + : State->add(ResSym); else if (const MemRegion *Reg = ResVal.getAsRegion()) State = State->add(Reg); else return; // no result to propagate to const NoteTag *BinOpTag = - NullDerivedRes ? nullptr // note will be added in BugVisitor - : C.getNoteTag("Binary operator has ambiguous provenance"); + IsInvalidCap ? nullptr // note will be added in BugVisitor + : C.getNoteTag("Binary operator has ambiguous provenance"); C.addTransition(State, N, BinOpTag); } @@ -365,19 +397,26 @@ void ProvenanceSourceChecker::checkPostStmt(const BinaryOperator *BO, bool const RHSActiveProv = !justConverted2IntCap(RHS, C.getASTContext()); ExplodedNode *N; - bool const NullDerivedRes = LHSIsNullDerived && RHSIsNullDerived; + bool InvalidCap; if (LHSActiveProv && RHSActiveProv && !IsSub) { N = emitAmbiguousProvenanceWarn(BO, C, LHSIsAddr, LHSIsNullDerived, - RHSIsAddr, RHSIsNullDerived); + RHSIsAddr, RHSIsNullDerived); + if (!N) + N = C.getPredecessor(); + InvalidCap = false; + } else if (IsSub && LHSIsAddr && RHSIsAddr) { + N = emitPtrdiffAsIntCapWarn(BO, C); if (!N) N = C.getPredecessor(); - } else if (NullDerivedRes) { + InvalidCap = true; + } else if (LHSIsNullDerived && (RHSIsNullDerived || IsSub)) { N = C.getPredecessor(); + InvalidCap = true; } else return; // Propagate info for result - propagateProvenanceInfoForBinOp(N, BO, C, NullDerivedRes); + propagateProvenanceInfoForBinOp(N, BO, C, InvalidCap); } void ProvenanceSourceChecker::checkDeadSymbols(SymbolReaper &SymReaper, @@ -385,11 +424,11 @@ void ProvenanceSourceChecker::checkDeadSymbols(SymbolReaper &SymReaper, ProgramStateRef State = C.getState(); bool Removed = false; - const NullDerivedCapTy &Set = State->get(); + const InvalidCapTy &Set = State->get(); for (const auto &Sym : Set) { if (!SymReaper.isDead(Sym)) continue; - State = State->remove(Sym); + State = State->remove(Sym); Removed = true; } @@ -416,7 +455,7 @@ static void describeCast(raw_ostream &OS, const CastExpr *CE, } PathDiagnosticPieceRef -ProvenanceSourceChecker::NullDerivedCapBugVisitor::VisitNode( +ProvenanceSourceChecker::InvalidCapBugVisitor::VisitNode( const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) { @@ -434,7 +473,7 @@ ProvenanceSourceChecker::NullDerivedCapBugVisitor::VisitNode( if (Sym != N->getSVal(CE).getAsSymbol()) return nullptr; - if (!N->getState()->contains(Sym)) + if (!N->getState()->contains(Sym)) return nullptr; OS << "NULL-derived capability: "; @@ -445,15 +484,20 @@ ProvenanceSourceChecker::NullDerivedCapBugVisitor::VisitNode( || BinaryOperator::isMultiplicativeOp(OpCode) || BinaryOperator::isBitwiseOp(OpCode))) return nullptr; + bool const IsSub = OpCode == clang::BO_Sub || OpCode == clang::BO_SubAssign; if (!BO->getType()->isIntCapType() || Sym != N->getSVal(BO).getAsSymbol()) return nullptr; - if (!N->getState()->contains(Sym)) + if (!N->getState()->contains(Sym)) return nullptr; - OS << "Result of '" << BO->getOpcodeStr() - << "' is a NULL-derived capability"; + OS << "Result of '" << BO->getOpcodeStr() << "'"; + if (hasNoProvenance(N->getState(), N->getSVal(BO->getLHS())) && + (hasNoProvenance(N->getState(), N->getSVal(BO->getRHS())) || IsSub)) + OS << " is a NULL-derived capability"; + else + OS << " is an invalid capability"; } else return nullptr; diff --git a/clang/test/Analysis/Checkers/CHERI/provenance-source.c b/clang/test/Analysis/Checkers/CHERI/provenance-source.c index 7bcb77cec688..adaa1222e11c 100644 --- a/clang/test/Analysis/Checkers/CHERI/provenance-source.c +++ b/clang/test/Analysis/Checkers/CHERI/provenance-source.c @@ -38,7 +38,7 @@ char no_prov(int d, char p) { intptr_t s = a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS and RHS were derived from NULL}} // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} - return *(char*)s; // expected-warning{{NULL-derived capability is used as pointer}} + return *(char*)s; // expected-warning{{Capability with ambiguous provenance is used as pointer}} } char right_prov_cond(int d, char *p, int x) { @@ -54,7 +54,6 @@ char right_prov_cond(int d, char *p, int x) { // expected-warning@-1{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from NULL, RHS was derived from pointer}} // expected-warning@-2{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} return *(char*)s;// expected-warning{{Capability with ambiguous provenance is used as pointer}} - // expected-warning@-1{{NULL-derived capability is used as pointer}} } intptr_t no_bug(int *p, intptr_t *u) { @@ -88,7 +87,7 @@ char add_var(int *p, int x, int c) { int * null_derived(int x) { intptr_t u = (intptr_t)x; - return (int*)u; // expected-warning{{NULL-derived capability is used as pointer}} + return (int*)u; // expected-warning{{Invalid capability is used as pointer}} } uintptr_t fn1(char *str, int f) { @@ -99,10 +98,18 @@ uintptr_t fn1(char *str, int f) { // expected-warning@-2{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} } -int fp1(char *s1, char *s2) { +char* ptr_diff(char *s1, char *s2) { intptr_t a = (intptr_t)s1; intptr_t b = (intptr_t)s2; - return a - b; + intptr_t d = a - b; // expected-warning{{Pointer difference as capability}} + return (char*)d; // expected-warning{{Invalid capability is used as pointer}} +} + +char* ptr_diff2(int x, char *s) { + intptr_t a = (intptr_t)x; + intptr_t b = (intptr_t)s; + intptr_t d = a - b; + return (char*)d; // expected-warning{{Invalid capability is used as pointer}} } void *fp2(char *p) { @@ -137,7 +144,7 @@ uintptr_t fn2(char *a, char *b) { char *ptrdiff(char *a, unsigned x) { intptr_t ip = ((ptrdiff_t)a | (intptr_t)x); - char *p = (char*) ip; // expected-warning{{NULL-derived capability is used as pointer}} + char *p = (char*) ip; // expected-warning{{Invalid capability is used as pointer}} return p; } From dc71e375f50f1267625be6673dd50dbbe3f5a577 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 16 Feb 2023 11:44:41 +0000 Subject: [PATCH 08/77] [CHERI-CSA] Add CapabilityCopyChecker Detects tag-stripping loads and stores that may be used to copy or swap capabilities --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../Checkers/CHERI/CapabilityCopyChecker.cpp | 284 ++++++++++++++++++ .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../Analysis/Checkers/CHERI/capability-copy.c | 148 +++++++++ 4 files changed, 437 insertions(+) create mode 100644 clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp create mode 100644 clang/test/Analysis/Checkers/CHERI/capability-copy.c diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 1274d6f06157..60feaeb9e52b 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1752,4 +1752,8 @@ def ProvenanceSourceChecker : Checker<"ProvenanceSourceChecker">, HelpText<"Check expressions with ambiguous provenance source.">, Documentation; +def CapabilityCopyChecker : Checker<"CapabilityCopyChecker">, + HelpText<"Check tag-stripping memory copy.">, + Documentation; + } // end alpha.cheri diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp new file mode 100644 index 000000000000..907fc5bda28f --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -0,0 +1,284 @@ +//===-- CapabilityCopyChecker.cpp - Capability Copy Checker -*- 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 checker that detects tag-stripping loads and stores that +// may be used to copy or swap capabilities +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { + +struct CHERITagState { +private: + enum Kind { Unknown, Tagged, MayBeTagged, Untagged } K; + CHERITagState(Kind InK) : K(InK) { } + +public: + bool isTagged() const { return K == Tagged; } + bool mayBeTagged() const { return K == MayBeTagged; } + bool isUntagged() const { return K == Untagged; } + bool isKnown() const { return K != Unknown; } + + static CHERITagState getTagged() { return CHERITagState(Tagged); } + static CHERITagState getMayBeTagged() { return CHERITagState(MayBeTagged); } + static CHERITagState getUntagged() { return CHERITagState(Untagged); } + static CHERITagState getUnknown() { return CHERITagState(Unknown); } + + + bool operator==(const CHERITagState &X) const { + return K == X.K; + } + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(K); + } +}; + +class CapabilityCopyChecker : public Checker, + check::Location, + check::Bind> { + std::unique_ptr UseCapAsNonCap; + std::unique_ptr StoreCapAsNonCap; + +public: + CapabilityCopyChecker(); + + void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; + void checkLocation(SVal l, bool isLoad, const Stmt *S, + CheckerContext &C) const; + void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; + +private: + bool checkBinaryOpArg(CheckerContext &C, const Expr* E) const; + +}; +} // namespace + +REGISTER_SET_WITH_PROGRAMSTATE(VoidPtrArgDeref, const MemRegion *) + +CapabilityCopyChecker::CapabilityCopyChecker() { + UseCapAsNonCap.reset( + new BugType(this, + "Part of capability value used in binary operator", + "CHERI portability")); + StoreCapAsNonCap.reset( + new BugType(this, + "Tag-stripping copy of capability", + "CHERI portability")); +} + +static bool isPointerToCapTy(const QualType Type, ASTContext &Ctx) { + if (!Type->isPointerType()) + return false; + return Type->getPointeeType()->isCHERICapabilityType(Ctx, true); +} + +static bool isNonCapScalarType(QualType T, ASTContext &C) { + if (!T->isScalarType()) + return false; + if (T->isCHERICapabilityType(C, true)) + return false; + return true; +} + +static const MemRegion *stripNonCapShift(const MemRegion * R, ASTContext &ASTCtx) { + const auto *ER = dyn_cast(R); + if (!ER) + return R; + + if (!isNonCapScalarType(ER->getValueType(), ASTCtx)) + return R; + + return ER->getSuperRegion(); +} + +static bool isVoidPtrArgRegion(const MemRegion *Reg) { + // 1. Reg is pointed by void* symbol + const SymbolicRegion *SymReg = Reg->getSymbolicBase(); + if (!SymReg) + return false; + + SymbolRef Sym = SymReg->getSymbol(); + if (!Sym->getType()->isVoidPointerType()) + return false; + + // 2. void* symbol is function argument + const MemRegion *BaseRegOrigin = Sym->getOriginRegion(); + return BaseRegOrigin + && BaseRegOrigin->getMemorySpace()->hasStackParametersStorage(); +} + +static CHERITagState getTagState(SVal Val, CheckerContext &C) { + if (Val.isUnknownOrUndef()) + return CHERITagState::getUnknown(); + + if (Val.isConstant()) + return CHERITagState::getUntagged(); + + if (Val.getAsRegion()) + return CHERITagState::getTagged(); + + if (SymbolRef Sym = Val.getAsSymbol()) { + if (const MemRegion *MR = Sym->getOriginRegion()) { + const MemRegion *SuperReg = stripNonCapShift(MR, C.getASTContext()); + if (isVoidPtrArgRegion(SuperReg)) + return CHERITagState::getMayBeTagged(); + if (MR != SuperReg && isa(SuperReg)) { + const SVal &SuperVal = C.getState()->getSVal(SuperReg); + if (Val != SuperVal) + return getTagState(SuperVal, C); + } + } + } + + return CHERITagState::getUnknown(); +} + +static unsigned getCapabilityTypeSize(ASTContext &ASTCtx) { + return ASTCtx.getTypeSize(ASTCtx.VoidPtrTy); +} + +static CharUnits getCapabilityTypeAlign(ASTContext &ASTCtx) { + return ASTCtx.getTypeAlignInChars(ASTCtx.VoidPtrTy); +} + +void CapabilityCopyChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, + CheckerContext &C) const { + if (!isLoad) + return; + + const MemRegion *R = l.getAsRegion(); + if (!R || !R->hasStackParametersStorage()) + return; + + // Get ArgVal + ProgramStateRef State = C.getState(); + QualType const Ty = l.getType(C.getASTContext()); + QualType const ArgValTy = Ty->getPointeeType(); + if (!ArgValTy->isVoidPointerType()) + return; + + /* Loading VoidPtr function argument */ + SVal ArgVal = State->getSVal(R, ArgValTy); + const MemRegion *ArgValAsRegion = ArgVal.getAsRegion(); + if (!ArgValAsRegion) + return; + + // If argument has value from caller, CharTy will not be used + SVal ArgValDeref = State->getSVal(ArgValAsRegion, C.getASTContext().CharTy); + + const NoteTag *Tag; + if (const auto *ArgValDerefAsRegion = ArgValDeref.getAsRegion()) { + Tag = C.getNoteTag("void* argument points to capability"); + State = State->add(ArgValDerefAsRegion); + } else if (ArgValDeref.getAsSymbol()) { + Tag = C.getNoteTag("void* argument may be a pointer to capability"); + } else + return; + + C.addTransition(State, C.getPredecessor(), Tag); +} + +static bool isCapabilityStorage(CheckerContext &C, const MemRegion *R) { + const MemRegion *BaseReg = stripNonCapShift(R, C.getASTContext()); + if (const auto *SymR = dyn_cast(BaseReg)) { + QualType const Ty = SymR->getSymbol()->getType(); + if (Ty->isVoidPointerType()) + return true; + return isPointerToCapTy(Ty, C.getASTContext()); + } + return false; +} + +void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, + CheckerContext &C) const { + const MemRegion *MR = L.getAsRegion(); + if (!MR) + return; + + const QualType &PointeeTy = L.getType(C.getASTContext())->getPointeeType(); + if (!isNonCapScalarType(PointeeTy, C.getASTContext())) + return; + + /* Non-capability scalar store */ + const CHERITagState &ValTag = getTagState(V, C); + if ((ValTag.isTagged() || ValTag.mayBeTagged()) + && isCapabilityStorage(C, MR)) { + /* Storing capability to capability storage as non-cap*/ + if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { + auto W = std::make_unique( + *StoreCapAsNonCap, "Tag-stripping store of a capability", + ErrNode); + W->addRange(S->getSourceRange()); + C.emitReport(std::move(W)); + return; + } + } +} + +bool CapabilityCopyChecker::checkBinaryOpArg(CheckerContext &C, const Expr* E) const { + ASTContext &ASTCtx = C.getASTContext(); + if (!isNonCapScalarType(E->getType(), ASTCtx)) + return false; + + const SVal &Val = C.getSVal(E); + const MemRegion *MR = Val.getAsRegion(); + if (!MR) { + // Check if Val is a part of capability + SymbolRef Sym = Val.getAsSymbol(); + if (!Sym) + return false; + const MemRegion *OrigRegion = Sym->getOriginRegion(); + if (!OrigRegion) + return false; + const MemRegion *SReg = stripNonCapShift(OrigRegion, C.getASTContext()); + const SVal &WiderVal = C.getState()->getSVal(SReg, ASTCtx.CharTy); + MR = WiderVal.getAsRegion(); + if (!MR) + return false; + } + + if (C.getState()->contains(MR)) { + /* Pointer to capability passed as void* argument */ + if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { + auto W = std::make_unique( + *UseCapAsNonCap, + "Part of capability representation used as argument in binary " + "operator", + ErrNode); + W->addRange(E->getSourceRange()); + C.emitReport(std::move(W)); + return true; + } + } + return false; +} + +void CapabilityCopyChecker::checkPostStmt(const BinaryOperator *BO, + CheckerContext &C) const { + if (BO->isAssignmentOp()) + return; + + checkBinaryOpArg(C, BO->getLHS()) || checkBinaryOpArg(C, BO->getRHS()); +} + +void ento::registerCapabilityCopyChecker(CheckerManager &mgr) { + mgr.registerChecker(); + +} + +bool ento::shouldRegisterCapabilityCopyChecker(const CheckerManager &Mgr) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index d7aeed81fc21..dcda2b092066 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -25,6 +25,7 @@ add_clang_library(clangStaticAnalyzerCheckers CheckSizeofPointer.cpp CheckerDocumentation.cpp CHERI/ProvenanceSourceChecker.cpp + CHERI/CapabilityCopyChecker.cpp ChrootChecker.cpp CloneChecker.cpp ContainerModeling.cpp diff --git a/clang/test/Analysis/Checkers/CHERI/capability-copy.c b/clang/test/Analysis/Checkers/CHERI/capability-copy.c new file mode 100644 index 000000000000..71e81a1491cf --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy.c @@ -0,0 +1,148 @@ +// RUN: %cheri_purecap_cc1 -analyze -analyzer-checker=core,alpha.cheri.CapabilityCopyChecker -verify %s + +// RUN: %cheri_purecap_cc1 -analyze -analyzer-checker=core,alpha.cheri.ProvenanceSourceChecker,alpha.cheri.CapabilityCopyChecker -verify %s + +typedef __intcap_t intptr_t; +typedef __uintcap_t uintptr_t; + +#define BLOCK_TYPE uintptr_t +#define BLOCK_SIZE sizeof(BLOCK_TYPE) + +typedef __typeof__(sizeof(int)) size_t; +extern void * malloc(size_t); +extern void *memmove(void *dest, const void *src, size_t n); +extern void free(void *ptr); + +static int x; + +// ===== Tag-stripping copy ===== +void copy_intptr_byte(int **ppy) { + int *px = &x; + int **ppx = &px; + void *q = ppx; + char *s = (char*)q; + *(char*)ppy = *s; // expected-warning{{Tag-stripping store of a capability}} +} + +void copy_intptr_byte2(int *px, int **ppy) { + int **ppx = &px; + void *q = ppx; + char *s = (char*)q; + *(char*)ppy = *s; // expected-warning{{Tag-stripping store of a capability}} +} + +void copy_int_byte(int *py) { + int *px = &x; + void *q = px; + char *s = (char*)q; + *(char*)py = *s; // no-warning: copying int value +} + +void copy_intptr(int **ppy) { + int *px = &x; + int **ppx = &px; + void *q = ppx; + int **v = (int**)q; + *ppy = *v; // no-warning: copy as int* +} + +void swapfunc(void *a, void *b, int n) { + long i = (n); + char *pi = (char *)(a); + char *pj = (char *)(b); + do { + char t = *pi; + *pi++ = *pj; // expected-warning{{Tag-stripping store of a capability}} + *pj++ = t; // expected-warning{{Tag-stripping store of a capability}} + } while (--i > 0); +} + +void *realloc_impl(void *ptr, size_t size) { + void *dst = malloc(size); + if (size <= sizeof(size)) { + size_t *mcsrc = (size_t *)(ptr); + size_t *mcdst = (size_t *)(dst); + *mcdst = *mcsrc; // expected-warning{{Tag-stripping store of a capability}} + } else + memmove(dst, ptr, size); + free(ptr); + return dst; +} + +void memcpy_impl(void* src0, void *dst0, size_t len) { + char *src = src0; + char *dst = dst0; + + if (len < sizeof(BLOCK_TYPE)) + while (len--) + *dst++ = *src++; // expected-warning{{Tag-stripping store of a capability}} +} + +char voidptr_arg_load1(void *q) { + char *s = (char*)q; + return *s; +} + +void voidptr_arg_store1(void *q) { + char *s = (char*)q; + *s = 42; +} + +char fp_malloc() { + void *q = malloc(100); + char *s = (char*)q; // no warning + return *s; +} + +char fp_init_str(void) { + char s[] = "String literal"; // no warning + return s[3]; +} + +// ===== Part of capability representation used as argument in binary operator ===== + +int hash_no_call(void *key0, size_t len) { + char *k = key0; + int h = 0; + while (len--) + h = (h << 5) + *k++; + return h; +} + +int hash(void *key0, size_t len) { + char *k = key0; + int h = 0; + while (len--) + h = (h << 5) + *k++; // expected-warning{{Part of capability representation used as argument in binary operator}} + return h; +} + +int ptr_hash(void) { + int *p = &x; + return hash(&p, sizeof(int*)); +} + +int memcmp_impl_no_call(const void* m1, const void *m2, size_t len) { + const char *s1 = m1; + const char *s2 = m2; + + while (len--) + if (*s1 != *s2) + return *s1 - *s2; + return 0; +} + +int memcmp_impl(const void* m1, const void *m2, size_t len) { + const char *s1 = m1; + const char *s2 = m2; + + while (len--) + if (*s1 != *s2) // expected-warning{{Part of capability representation used as argument in binary operator}} + return *s1 - *s2; // expected-warning{{Part of capability representation used as argument in binary operator}} + return 0; +} + +int ptr_cmp(int *x, int *y) { + return memcmp_impl(&x, &y, sizeof(int*)); +} + From 66daaa8365d05670b8682a89bdfe6a1a813368c0 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 13 Mar 2023 23:19:44 +0000 Subject: [PATCH 09/77] [CHERI_CSA] CapabilityCopyChecker: suppress for short loops --- .../Checkers/CHERI/CapabilityCopyChecker.cpp | 122 ++++++++++++++++-- .../Analysis/Checkers/CHERI/capability-copy.c | 17 ++- 2 files changed, 122 insertions(+), 17 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index 907fc5bda28f..03af16a416b5 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -15,6 +15,8 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" using namespace clang; using namespace ento; @@ -48,7 +50,8 @@ struct CHERITagState { class CapabilityCopyChecker : public Checker, check::Location, - check::Bind> { + check::Bind, + check::BranchCondition> { std::unique_ptr UseCapAsNonCap; std::unique_ptr StoreCapAsNonCap; @@ -59,6 +62,7 @@ class CapabilityCopyChecker : public Checker, void checkLocation(SVal l, bool isLoad, const Stmt *S, CheckerContext &C) const; void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; + void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const; private: bool checkBinaryOpArg(CheckerContext &C, const Expr* E) const; @@ -67,6 +71,7 @@ class CapabilityCopyChecker : public Checker, } // namespace REGISTER_SET_WITH_PROGRAMSTATE(VoidPtrArgDeref, const MemRegion *) +REGISTER_LIST_WITH_PROGRAMSTATE(WhileBoundVar, SymbolRef) CapabilityCopyChecker::CapabilityCopyChecker() { UseCapAsNonCap.reset( @@ -202,6 +207,53 @@ static bool isCapabilityStorage(CheckerContext &C, const MemRegion *R) { return false; } +static auto whileConditionMatcher() { + using namespace clang::ast_matchers; + + // (--len) + // FIXME: PreDec for do-while; PostDec for while + auto U = + unaryOperator(hasOperatorName("--"), hasUnaryOperand(expr().bind("len"))); + // (--len > 0) + auto BO = binaryOperation( + hasAnyOperatorName("!=", ">"), + hasLHS(U), + hasRHS(ignoringImplicit(integerLiteral(equals(0)))) + ); + return stmt(anyOf(U, BO)); +} + +static bool isInsideSmallConstantBoundLoop(const Stmt *S, CheckerContext &C, + unsigned BlockSize) { + ASTContext &ASTCtx = C.getASTContext(); + SValBuilder &SVB = C.getSValBuilder(); + unsigned CapSize = ASTCtx.getTypeSize(ASTCtx.VoidPtrTy); + unsigned IterNum4CapCopy = CapSize / BlockSize; + const NonLoc &ItVal = SVB.makeIntVal(IterNum4CapCopy, true); + + auto BoundVarList = C.getState()->get(); + for (auto &&V : BoundVarList) { + auto SmallLoop = + SVB.evalBinOpNN(C.getState(), clang::BO_GE, nonloc::SymbolVal(V), ItVal, + SVB.getConditionType()); + if (auto LC = SmallLoop.getAs()) + if (!C.getState()->assume(*LC, true)) + return true; + return false; + } + + using namespace clang::ast_matchers; + if (C.blockCount() == 1) { + // first iter + auto doWhileStmtMatcher = doStmt(hasCondition(whileConditionMatcher())); + auto M = match(stmt(hasParent(doWhileStmtMatcher)), *S, C.getASTContext()); + if (!M.empty()) + return true; // decide on second iteration + } + + return false; +} + void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const { const MemRegion *MR = L.getAsRegion(); @@ -211,20 +263,26 @@ void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, const QualType &PointeeTy = L.getType(C.getASTContext())->getPointeeType(); if (!isNonCapScalarType(PointeeTy, C.getASTContext())) return; - /* Non-capability scalar store */ + const CHERITagState &ValTag = getTagState(V, C); - if ((ValTag.isTagged() || ValTag.mayBeTagged()) - && isCapabilityStorage(C, MR)) { - /* Storing capability to capability storage as non-cap*/ - if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { - auto W = std::make_unique( - *StoreCapAsNonCap, "Tag-stripping store of a capability", - ErrNode); - W->addRange(S->getSourceRange()); - C.emitReport(std::move(W)); - return; - } + if (!ValTag.isTagged() && !ValTag.mayBeTagged()) + return; + + if (!isCapabilityStorage(C, MR)) + return; + + // Skip if loop bound is not big enough to copy capability + unsigned BlockSize = C.getASTContext().getTypeSize(PointeeTy); + if (isInsideSmallConstantBoundLoop(S, C, BlockSize)) + return; + + /* Storing capability to capability storage as non-cap*/ + if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { + auto W = std::make_unique( + *StoreCapAsNonCap, "Tag-stripping store of a capability", ErrNode); + W->addRange(S->getSourceRange()); + C.emitReport(std::move(W)); } } @@ -274,6 +332,44 @@ void CapabilityCopyChecker::checkPostStmt(const BinaryOperator *BO, checkBinaryOpArg(C, BO->getLHS()) || checkBinaryOpArg(C, BO->getRHS()); } +void CapabilityCopyChecker::checkBranchCondition(const Stmt *Condition, + CheckerContext &Ctx) const { + using namespace clang::ast_matchers; + // TODO: do-while, merge with second matcher + auto childOfWhile = stmt(hasParent(stmt(anyOf(whileStmt(), doStmt())))); + auto L = match(childOfWhile, *Condition, Ctx.getASTContext()); + if (L.empty()) + return; + + auto whileCond = whileConditionMatcher(); + + auto M = match(whileCond, *Condition, Ctx.getASTContext()); + if (M.size() != 1) + return; + + const SVal &CondVal = Ctx.getSVal(Condition); + if (Ctx.getSVal(Condition).isUnknownOrUndef()) + return; + + ProgramStateRef ThenSt, ElseSt; + std::tie(ThenSt, ElseSt) = + Ctx.getState()->assume(CondVal.castAs()); + for (auto I : M) { + auto L = I.getNodeAs("len"); + if (SymbolRef ISym = Ctx.getSVal(L).getAsSymbol()) { + if (ThenSt) + ThenSt = ThenSt->add(ISym); + if (ElseSt) + ElseSt = ElseSt->set(llvm::ImmutableList()); + } else + return; + } + if (ThenSt) + Ctx.addTransition(ThenSt); + if (ElseSt) + Ctx.addTransition(ElseSt); +} + void ento::registerCapabilityCopyChecker(CheckerManager &mgr) { mgr.registerChecker(); diff --git a/clang/test/Analysis/Checkers/CHERI/capability-copy.c b/clang/test/Analysis/Checkers/CHERI/capability-copy.c index 71e81a1491cf..db9d7852163d 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-copy.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy.c @@ -46,8 +46,8 @@ void copy_intptr(int **ppy) { *ppy = *v; // no-warning: copy as int* } -void swapfunc(void *a, void *b, int n) { - long i = (n); +static void swapfunc(void *a, void *b, int n) { + long i = n; char *pi = (char *)(a); char *pj = (char *)(b); do { @@ -69,11 +69,20 @@ void *realloc_impl(void *ptr, size_t size) { return dst; } -void memcpy_impl(void* src0, void *dst0, size_t len) { +void memcpy_impl_good(void* src0, void *dst0, size_t len) { char *src = src0; char *dst = dst0; - if (len < sizeof(BLOCK_TYPE)) + if ((len < sizeof(BLOCK_TYPE)) || ((long)src & (BLOCK_SIZE - 1)) || ((long)dst & (BLOCK_SIZE - 1))) + while (len--) + *dst++ = *src++; // no-warning +} + +void memcpy_impl_bad(void* src0, void *dst0, size_t len) { + char *src = src0; + char *dst = dst0; + + if (len < sizeof(BLOCK_TYPE)+1) while (len--) *dst++ = *src++; // expected-warning{{Tag-stripping store of a capability}} } From fb9707f6dbb837b6ff074b32024278fd5b624fd6 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 15 Mar 2023 22:29:21 +0000 Subject: [PATCH 10/77] [CHERI_CSA] CapabilityCopyChecker: suppress for unaligned ptr --- .../Checkers/CHERI/CapabilityCopyChecker.cpp | 91 ++++++++++++++++--- .../Analysis/Checkers/CHERI/capability-copy.c | 11 ++- 2 files changed, 86 insertions(+), 16 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index 03af16a416b5..7fc4b6028888 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -65,12 +65,13 @@ class CapabilityCopyChecker : public Checker, void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const; private: - bool checkBinaryOpArg(CheckerContext &C, const Expr* E) const; + ExplodedNode *checkBinaryOpArg(CheckerContext &C, const Expr* E) const; }; } // namespace REGISTER_SET_WITH_PROGRAMSTATE(VoidPtrArgDeref, const MemRegion *) +REGISTER_SET_WITH_PROGRAMSTATE(UnalignedPtr, const MemRegion *) REGISTER_LIST_WITH_PROGRAMSTATE(WhileBoundVar, SymbolRef) CapabilityCopyChecker::CapabilityCopyChecker() { @@ -110,6 +111,9 @@ static const MemRegion *stripNonCapShift(const MemRegion * R, ASTContext &ASTCtx } static bool isVoidPtrArgRegion(const MemRegion *Reg) { + if (!Reg) + return false; + // 1. Reg is pointed by void* symbol const SymbolicRegion *SymReg = Reg->getSymbolicBase(); if (!SymReg) @@ -125,7 +129,8 @@ static bool isVoidPtrArgRegion(const MemRegion *Reg) { && BaseRegOrigin->getMemorySpace()->hasStackParametersStorage(); } -static CHERITagState getTagState(SVal Val, CheckerContext &C) { +static CHERITagState getTagState(SVal Val, CheckerContext &C, + bool AcceptUnaligned = true) { if (Val.isUnknownOrUndef()) return CHERITagState::getUnknown(); @@ -138,7 +143,8 @@ static CHERITagState getTagState(SVal Val, CheckerContext &C) { if (SymbolRef Sym = Val.getAsSymbol()) { if (const MemRegion *MR = Sym->getOriginRegion()) { const MemRegion *SuperReg = stripNonCapShift(MR, C.getASTContext()); - if (isVoidPtrArgRegion(SuperReg)) + if (isVoidPtrArgRegion(SuperReg) && + (AcceptUnaligned || !C.getState()->contains(SuperReg))) return CHERITagState::getMayBeTagged(); if (MR != SuperReg && isa(SuperReg)) { const SVal &SuperVal = C.getState()->getSVal(SuperReg); @@ -196,8 +202,11 @@ void CapabilityCopyChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, C.addTransition(State, C.getPredecessor(), Tag); } -static bool isCapabilityStorage(CheckerContext &C, const MemRegion *R) { +static bool isCapabilityStorage(CheckerContext &C, const MemRegion *R, + bool AcceptUnaligned = true) { const MemRegion *BaseReg = stripNonCapShift(R, C.getASTContext()); + if (!AcceptUnaligned && C.getState()->contains(BaseReg)) + return false; if (const auto *SymR = dyn_cast(BaseReg)) { QualType const Ty = SymR->getSymbol()->getType(); if (Ty->isVoidPointerType()) @@ -227,7 +236,7 @@ static bool isInsideSmallConstantBoundLoop(const Stmt *S, CheckerContext &C, unsigned BlockSize) { ASTContext &ASTCtx = C.getASTContext(); SValBuilder &SVB = C.getSValBuilder(); - unsigned CapSize = ASTCtx.getTypeSize(ASTCtx.VoidPtrTy); + unsigned CapSize = getCapabilityTypeSize(ASTCtx); unsigned IterNum4CapCopy = CapSize / BlockSize; const NonLoc &ItVal = SVB.makeIntVal(IterNum4CapCopy, true); @@ -286,10 +295,10 @@ void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, } } -bool CapabilityCopyChecker::checkBinaryOpArg(CheckerContext &C, const Expr* E) const { +ExplodedNode *CapabilityCopyChecker::checkBinaryOpArg(CheckerContext &C, const Expr* E) const { ASTContext &ASTCtx = C.getASTContext(); if (!isNonCapScalarType(E->getType(), ASTCtx)) - return false; + return nullptr; const SVal &Val = C.getSVal(E); const MemRegion *MR = Val.getAsRegion(); @@ -297,15 +306,15 @@ bool CapabilityCopyChecker::checkBinaryOpArg(CheckerContext &C, const Expr* E) c // Check if Val is a part of capability SymbolRef Sym = Val.getAsSymbol(); if (!Sym) - return false; + return nullptr; const MemRegion *OrigRegion = Sym->getOriginRegion(); if (!OrigRegion) - return false; + return nullptr; const MemRegion *SReg = stripNonCapShift(OrigRegion, C.getASTContext()); const SVal &WiderVal = C.getState()->getSVal(SReg, ASTCtx.CharTy); MR = WiderVal.getAsRegion(); if (!MR) - return false; + return nullptr; } if (C.getState()->contains(MR)) { @@ -318,18 +327,70 @@ bool CapabilityCopyChecker::checkBinaryOpArg(CheckerContext &C, const Expr* E) c ErrNode); W->addRange(E->getSourceRange()); C.emitReport(std::move(W)); - return true; + return ErrNode; } } - return false; + return nullptr; +} + +static const MemRegion *isCapAlignCheck(const BinaryOperator *BO, CheckerContext &C) { + if (BO->getOpcode() != clang::BO_And) + return nullptr; + + long CapAlignMask = + getCapabilityTypeAlign(C.getASTContext()).getQuantity() - 1; + + const SVal &RHSVal = C.getSVal(BO->getRHS()); + if (!RHSVal.isConstant(CapAlignMask)) + return nullptr; + + return C.getSVal(BO->getLHS()).getAsRegion(); } void CapabilityCopyChecker::checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const { - if (BO->isAssignmentOp()) - return; + ExplodedNode *N = nullptr; + + /* Check for capability repr bytes used in arithmetic */ + if (!BO->isAssignmentOp() || BO->isCompoundAssignmentOp()) { + N = checkBinaryOpArg(C, BO->getLHS()); + if (!N) + N = checkBinaryOpArg(C, BO->getRHS()); + } + if (!N) + N = C.getPredecessor(); + + /* Handle alignment check */ + if (auto ExprPtrReg = isCapAlignCheck(BO, C)) { + if (!isVoidPtrArgRegion(ExprPtrReg)) + return; + ProgramStateRef State = N->getState(); + + SVal AndVal = C.getSVal(BO); + if (AndVal.isUnknown()) { + const LocationContext *LCtx = C.getLocationContext(); + AndVal = C.getSValBuilder().conjureSymbolVal( + nullptr, BO, LCtx, BO->getType(), C.blockCount()); + State = State->BindExpr(BO, LCtx, AndVal); + } + + SValBuilder &SVB = C.getSValBuilder(); + auto PtrIsCapAligned = SVB.evalEQ(State, AndVal, SVB.makeIntVal(0, true)); + ProgramStateRef Aligned, NotAligned; + std::tie(Aligned, NotAligned) = + State->assume(PtrIsCapAligned.castAs()); + + if (NotAligned) { + // If void* argument value is not capability aligned, then it cannot + // be a pointer to capability + NotAligned = NotAligned->add(ExprPtrReg->StripCasts()); + C.addTransition(NotAligned); + } + + if (Aligned) + C.addTransition(Aligned); + } - checkBinaryOpArg(C, BO->getLHS()) || checkBinaryOpArg(C, BO->getRHS()); } void CapabilityCopyChecker::checkBranchCondition(const Stmt *Condition, diff --git a/clang/test/Analysis/Checkers/CHERI/capability-copy.c b/clang/test/Analysis/Checkers/CHERI/capability-copy.c index db9d7852163d..6fa0841e0f56 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-copy.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy.c @@ -73,9 +73,18 @@ void memcpy_impl_good(void* src0, void *dst0, size_t len) { char *src = src0; char *dst = dst0; + if ((len < sizeof(BLOCK_TYPE))) + while (len--) + *dst++ = *src++; // no-warn +} + +void memcpy_impl_unaligned(void* src0, void *dst0, size_t len) { + char *src = src0; + char *dst = dst0; + if ((len < sizeof(BLOCK_TYPE)) || ((long)src & (BLOCK_SIZE - 1)) || ((long)dst & (BLOCK_SIZE - 1))) while (len--) - *dst++ = *src++; // no-warning + *dst++ = *src++; // expected-warning{{Tag-stripping store of a capability}} } void memcpy_impl_bad(void* src0, void *dst0, size_t len) { From 9d24a624f60b8fa81c1d8aba38642361e1bd9e03 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 22 Jun 2023 12:22:03 +0100 Subject: [PATCH 11/77] [CHERI_CSA] CapabilityCopyChecker: silence for hybrid mode --- .../Checkers/CHERI/CapabilityCopyChecker.cpp | 54 +++++--- .../Checkers/CHERI/capability-copy-hybrid.c | 129 ++++++++++++++++++ ...ility-copy.c => capability-copy-purecap.c} | 2 - 3 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c rename clang/test/Analysis/Checkers/CHERI/{capability-copy.c => capability-copy-purecap.c} (96%) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index 7fc4b6028888..025f7891a820 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -62,7 +62,7 @@ class CapabilityCopyChecker : public Checker, void checkLocation(SVal l, bool isLoad, const Stmt *S, CheckerContext &C) const; void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; - void checkBranchCondition(const Stmt *Condition, CheckerContext &Ctx) const; + void checkBranchCondition(const Stmt *Condition, CheckerContext &C) const; private: ExplodedNode *checkBinaryOpArg(CheckerContext &C, const Expr* E) const; @@ -85,6 +85,10 @@ CapabilityCopyChecker::CapabilityCopyChecker() { "CHERI portability")); } +static bool isPureCapMode(const ASTContext &C) { + return C.getTargetInfo().areAllPointersCapabilities(); +} + static bool isPointerToCapTy(const QualType Type, ASTContext &Ctx) { if (!Type->isPointerType()) return false; @@ -169,6 +173,9 @@ void CapabilityCopyChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, CheckerContext &C) const { if (!isLoad) return; + ASTContext &ASTCtx = C.getASTContext(); + if (!isPureCapMode(ASTCtx)) + return; const MemRegion *R = l.getAsRegion(); if (!R || !R->hasStackParametersStorage()) @@ -176,7 +183,7 @@ void CapabilityCopyChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, // Get ArgVal ProgramStateRef State = C.getState(); - QualType const Ty = l.getType(C.getASTContext()); + QualType const Ty = l.getType(ASTCtx); QualType const ArgValTy = Ty->getPointeeType(); if (!ArgValTy->isVoidPointerType()) return; @@ -188,7 +195,7 @@ void CapabilityCopyChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, return; // If argument has value from caller, CharTy will not be used - SVal ArgValDeref = State->getSVal(ArgValAsRegion, C.getASTContext().CharTy); + SVal ArgValDeref = State->getSVal(ArgValAsRegion, ASTCtx.CharTy); const NoteTag *Tag; if (const auto *ArgValDerefAsRegion = ArgValDeref.getAsRegion()) { @@ -265,12 +272,16 @@ static bool isInsideSmallConstantBoundLoop(const Stmt *S, CheckerContext &C, void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const { + ASTContext &ASTCtx = C.getASTContext(); + if (!isPureCapMode(ASTCtx)) + return; + const MemRegion *MR = L.getAsRegion(); if (!MR) return; - const QualType &PointeeTy = L.getType(C.getASTContext())->getPointeeType(); - if (!isNonCapScalarType(PointeeTy, C.getASTContext())) + const QualType &PointeeTy = L.getType(ASTCtx)->getPointeeType(); + if (!isNonCapScalarType(PointeeTy, ASTCtx)) return; /* Non-capability scalar store */ @@ -282,7 +293,7 @@ void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, return; // Skip if loop bound is not big enough to copy capability - unsigned BlockSize = C.getASTContext().getTypeSize(PointeeTy); + unsigned BlockSize = ASTCtx.getTypeSize(PointeeTy); if (isInsideSmallConstantBoundLoop(S, C, BlockSize)) return; @@ -295,8 +306,11 @@ void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, } } -ExplodedNode *CapabilityCopyChecker::checkBinaryOpArg(CheckerContext &C, const Expr* E) const { +ExplodedNode *CapabilityCopyChecker::checkBinaryOpArg(CheckerContext &C, + const Expr* E) const { ASTContext &ASTCtx = C.getASTContext(); + if (!isPureCapMode(ASTCtx)) + return nullptr; if (!isNonCapScalarType(E->getType(), ASTCtx)) return nullptr; @@ -349,6 +363,10 @@ static const MemRegion *isCapAlignCheck(const BinaryOperator *BO, CheckerContext void CapabilityCopyChecker::checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const { + ASTContext &ASTCtx = C.getASTContext(); + if (!isPureCapMode(ASTCtx)) + return; + ExplodedNode *N = nullptr; /* Check for capability repr bytes used in arithmetic */ @@ -394,30 +412,34 @@ void CapabilityCopyChecker::checkPostStmt(const BinaryOperator *BO, } void CapabilityCopyChecker::checkBranchCondition(const Stmt *Condition, - CheckerContext &Ctx) const { + CheckerContext &C) const { + ASTContext &ASTCtx = C.getASTContext(); + if (!isPureCapMode(ASTCtx)) + return; + using namespace clang::ast_matchers; // TODO: do-while, merge with second matcher auto childOfWhile = stmt(hasParent(stmt(anyOf(whileStmt(), doStmt())))); - auto L = match(childOfWhile, *Condition, Ctx.getASTContext()); + auto L = match(childOfWhile, *Condition, ASTCtx); if (L.empty()) return; auto whileCond = whileConditionMatcher(); - auto M = match(whileCond, *Condition, Ctx.getASTContext()); + auto M = match(whileCond, *Condition, ASTCtx); if (M.size() != 1) return; - const SVal &CondVal = Ctx.getSVal(Condition); - if (Ctx.getSVal(Condition).isUnknownOrUndef()) + const SVal &CondVal = C.getSVal(Condition); + if (C.getSVal(Condition).isUnknownOrUndef()) return; ProgramStateRef ThenSt, ElseSt; std::tie(ThenSt, ElseSt) = - Ctx.getState()->assume(CondVal.castAs()); + C.getState()->assume(CondVal.castAs()); for (auto I : M) { auto L = I.getNodeAs("len"); - if (SymbolRef ISym = Ctx.getSVal(L).getAsSymbol()) { + if (SymbolRef ISym = C.getSVal(L).getAsSymbol()) { if (ThenSt) ThenSt = ThenSt->add(ISym); if (ElseSt) @@ -426,9 +448,9 @@ void CapabilityCopyChecker::checkBranchCondition(const Stmt *Condition, return; } if (ThenSt) - Ctx.addTransition(ThenSt); + C.addTransition(ThenSt); if (ElseSt) - Ctx.addTransition(ElseSt); + C.addTransition(ElseSt); } void ento::registerCapabilityCopyChecker(CheckerManager &mgr) { diff --git a/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c b/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c new file mode 100644 index 000000000000..6ee4cb0135cc --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c @@ -0,0 +1,129 @@ +// RUN: %cheri_cc1 -analyze -analyzer-checker=core,alpha.cheri.CapabilityCopyChecker -verify %s + +// Don't emit anywarnings fot hybrid mode +// expected-no-diagnostics + +#define BLOCK_TYPE long +#define BLOCK_SIZE sizeof(BLOCK_TYPE) + +typedef __typeof__(sizeof(int)) size_t; +extern void * malloc(size_t); +extern void *memmove(void *dest, const void *src, size_t n); +extern void free(void *ptr); + +static int x; + +// ===== Tag-stripping copy ===== +void copy_intptr_byte(int **ppy) { + int *px = &x; + int **ppx = &px; + void *q = ppx; + char *s = (char*)q; + *(char*)ppy = *s; +} + +void copy_intptr_byte2(int *px, int **ppy) { + int **ppx = &px; + void *q = ppx; + char *s = (char*)q; + *(char*)ppy = *s; +} + +static void swapfunc(void *a, void *b, int n) { + long i = n; + char *pi = (char *)(a); + char *pj = (char *)(b); + do { + char t = *pi; + *pi++ = *pj; + *pj++ = t; + } while (--i > 0); +} + +void *realloc_impl(void *ptr, size_t size) { + void *dst = malloc(size); + if (size <= sizeof(size)) { + size_t *mcsrc = (size_t *)(ptr); + size_t *mcdst = (size_t *)(dst); + *mcdst = *mcsrc; + } else + memmove(dst, ptr, size); + free(ptr); + return dst; +} + +void memcpy_impl_unaligned(void* src0, void *dst0, size_t len) { + char *src = src0; + char *dst = dst0; + + if ((len < sizeof(BLOCK_TYPE)) || ((long)src & (BLOCK_SIZE - 1)) || ((long)dst & (BLOCK_SIZE - 1))) + while (len--) + *dst++ = *src++; +} + +void memcpy_impl_bad(void* src0, void *dst0, size_t len) { + char *src = src0; + char *dst = dst0; + + if (len < sizeof(BLOCK_TYPE)+1) + while (len--) + *dst++ = *src++; +} + +char voidptr_arg_load1(void *q) { + char *s = (char*)q; + return *s; +} + +void voidptr_arg_store1(void *q) { + char *s = (char*)q; + *s = 42; +} + +// ===== Part of capability representation used as argument in binary operator ===== + +int hash_no_call(void *key0, size_t len) { + char *k = key0; + int h = 0; + while (len--) + h = (h << 5) + *k++; + return h; +} + +int hash(void *key0, size_t len) { + char *k = key0; + int h = 0; + while (len--) + h = (h << 5) + *k++; + return h; +} + +int ptr_hash(void) { + int *p = &x; + return hash(&p, sizeof(int*)); +} + +int memcmp_impl_no_call(const void* m1, const void *m2, size_t len) { + const char *s1 = m1; + const char *s2 = m2; + + while (len--) + if (*s1 != *s2) + return *s1 - *s2; + return 0; +} + +int memcmp_impl(const void* m1, const void *m2, size_t len) { + const char *s1 = m1; + const char *s2 = m2; + + while (len--) + if (*s1 != *s2) + return *s1 - *s2; + return 0; +} + +int ptr_cmp(int *x, int *y) { + return memcmp_impl(&x, &y, sizeof(int*)); +} + diff --git a/clang/test/Analysis/Checkers/CHERI/capability-copy.c b/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c similarity index 96% rename from clang/test/Analysis/Checkers/CHERI/capability-copy.c rename to clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c index 6fa0841e0f56..fe6ed24301c3 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-copy.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c @@ -1,7 +1,5 @@ // RUN: %cheri_purecap_cc1 -analyze -analyzer-checker=core,alpha.cheri.CapabilityCopyChecker -verify %s -// RUN: %cheri_purecap_cc1 -analyze -analyzer-checker=core,alpha.cheri.ProvenanceSourceChecker,alpha.cheri.CapabilityCopyChecker -verify %s - typedef __intcap_t intptr_t; typedef __uintcap_t uintptr_t; From 1152133efe79e9a2637a1a626a70f9dd765299a3 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 27 Jun 2023 18:05:58 +0100 Subject: [PATCH 12/77] [CHERI-CSA] CapabilityCopyChecker: char* as universal pointer --- .../Checkers/CHERI/CapabilityCopyChecker.cpp | 434 ++++++++++++------ .../Checkers/CHERI/capability-copy-purecap.c | 49 ++ 2 files changed, 344 insertions(+), 139 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index 025f7891a820..0196c808c59c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -11,12 +11,15 @@ // //===----------------------------------------------------------------------===// +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/ASTMatchers/ASTMatchFinder.h" -#include "clang/ASTMatchers/ASTMatchers.h" +#include +#include using namespace clang; using namespace ento; @@ -48,21 +51,35 @@ struct CHERITagState { } }; -class CapabilityCopyChecker : public Checker, - check::Location, - check::Bind, - check::BranchCondition> { +class CapabilityCopyChecker :public Checker, + check::PostStmt, + check::BranchCondition, + check::PreCall> { + std::unique_ptr UseCapAsNonCap; std::unique_ptr StoreCapAsNonCap; + using CheckFn = std::function; + const CallDescriptionMap CStringFn { + {{"strlen", 1}, 1}, + {{"strdup", 1}, 1}, + {{"strcpy", 2}, 3}, + {{"strcat", 2}, 3} + }; + public: CapabilityCopyChecker(); - void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; void checkLocation(SVal l, bool isLoad, const Stmt *S, CheckerContext &C) const; void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; - void checkBranchCondition(const Stmt *Condition, CheckerContext &C) const; + + void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; + void checkPostStmt(const ArraySubscriptExpr *E, CheckerContext &C) const; + void checkBranchCondition(const Stmt *Cond, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; private: ExplodedNode *checkBinaryOpArg(CheckerContext &C, const Expr* E) const; @@ -72,6 +89,7 @@ class CapabilityCopyChecker : public Checker, REGISTER_SET_WITH_PROGRAMSTATE(VoidPtrArgDeref, const MemRegion *) REGISTER_SET_WITH_PROGRAMSTATE(UnalignedPtr, const MemRegion *) +REGISTER_SET_WITH_PROGRAMSTATE(CString, const MemRegion *) REGISTER_LIST_WITH_PROGRAMSTATE(WhileBoundVar, SymbolRef) CapabilityCopyChecker::CapabilityCopyChecker() { @@ -85,17 +103,18 @@ CapabilityCopyChecker::CapabilityCopyChecker() { "CHERI portability")); } -static bool isPureCapMode(const ASTContext &C) { +namespace { +bool isPureCapMode(const ASTContext &C) { return C.getTargetInfo().areAllPointersCapabilities(); } -static bool isPointerToCapTy(const QualType Type, ASTContext &Ctx) { +bool isPointerToCapTy(const QualType Type, ASTContext &Ctx) { if (!Type->isPointerType()) return false; return Type->getPointeeType()->isCHERICapabilityType(Ctx, true); } -static bool isNonCapScalarType(QualType T, ASTContext &C) { +bool isNonCapScalarType(QualType T, ASTContext &C) { if (!T->isScalarType()) return false; if (T->isCHERICapabilityType(C, true)) @@ -103,38 +122,43 @@ static bool isNonCapScalarType(QualType T, ASTContext &C) { return true; } -static const MemRegion *stripNonCapShift(const MemRegion * R, ASTContext &ASTCtx) { +const MemRegion *stripNonCapShift(const MemRegion *R, ASTContext &ASTCtx) { const auto *ER = dyn_cast(R); if (!ER) return R; if (!isNonCapScalarType(ER->getValueType(), ASTCtx)) - return R; + return R; return ER->getSuperRegion(); } -static bool isVoidPtrArgRegion(const MemRegion *Reg) { +bool isGenericPointerType(const QualType T) { + return T->isVoidPointerType() || + (T->isPointerType() && T->getPointeeType()->isCharType()); +} + +bool isVoidOrCharPtrArgRegion(const MemRegion *Reg) { if (!Reg) - return false; + return false; // 1. Reg is pointed by void* symbol const SymbolicRegion *SymReg = Reg->getSymbolicBase(); if (!SymReg) - return false; + return false; SymbolRef Sym = SymReg->getSymbol(); - if (!Sym->getType()->isVoidPointerType()) - return false; + if (!isGenericPointerType(Sym->getType())) + return false; // 2. void* symbol is function argument const MemRegion *BaseRegOrigin = Sym->getOriginRegion(); - return BaseRegOrigin - && BaseRegOrigin->getMemorySpace()->hasStackParametersStorage(); + return BaseRegOrigin && + BaseRegOrigin->getMemorySpace()->hasStackParametersStorage(); } -static CHERITagState getTagState(SVal Val, CheckerContext &C, - bool AcceptUnaligned = true) { +CHERITagState getTagState(SVal Val, CheckerContext &C, + bool AcceptUnaligned = true) { if (Val.isUnknownOrUndef()) return CHERITagState::getUnknown(); @@ -146,9 +170,13 @@ static CHERITagState getTagState(SVal Val, CheckerContext &C, if (SymbolRef Sym = Val.getAsSymbol()) { if (const MemRegion *MR = Sym->getOriginRegion()) { + const ProgramStateRef S = C.getState(); const MemRegion *SuperReg = stripNonCapShift(MR, C.getASTContext()); - if (isVoidPtrArgRegion(SuperReg) && - (AcceptUnaligned || !C.getState()->contains(SuperReg))) + if (isa(SuperReg)) + return CHERITagState::getUnknown(); // not a part of capability + if (isVoidOrCharPtrArgRegion(SuperReg) && + (AcceptUnaligned || !S->contains(SuperReg)) && + !S->contains(SuperReg)) return CHERITagState::getMayBeTagged(); if (MR != SuperReg && isa(SuperReg)) { const SVal &SuperVal = C.getState()->getSVal(SuperReg); @@ -161,19 +189,37 @@ static CHERITagState getTagState(SVal Val, CheckerContext &C, return CHERITagState::getUnknown(); } -static unsigned getCapabilityTypeSize(ASTContext &ASTCtx) { +unsigned getCapabilityTypeSize(ASTContext &ASTCtx) { return ASTCtx.getTypeSize(ASTCtx.VoidPtrTy); } -static CharUnits getCapabilityTypeAlign(ASTContext &ASTCtx) { +CharUnits getCapabilityTypeAlign(ASTContext &ASTCtx) { return ASTCtx.getTypeAlignInChars(ASTCtx.VoidPtrTy); } +bool isCapabilityStorage(CheckerContext &C, const MemRegion *R, + bool AcceptUnaligned = true) { + const MemRegion *BaseReg = stripNonCapShift(R, C.getASTContext()); + if (!AcceptUnaligned && C.getState()->contains(BaseReg)) + return false; + if (C.getState()->contains(BaseReg)) + return false; + if (const auto *SymR = dyn_cast(BaseReg)) { + QualType const Ty = SymR->getSymbol()->getType(); + if (isGenericPointerType(Ty)) + return true; + return isPointerToCapTy(Ty, C.getASTContext()); + } + return false; +} + +} //namespace + void CapabilityCopyChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, CheckerContext &C) const { if (!isLoad) return; - ASTContext &ASTCtx = C.getASTContext(); + const ASTContext &ASTCtx = C.getASTContext(); if (!isPureCapMode(ASTCtx)) return; @@ -185,17 +231,17 @@ void CapabilityCopyChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, ProgramStateRef State = C.getState(); QualType const Ty = l.getType(ASTCtx); QualType const ArgValTy = Ty->getPointeeType(); - if (!ArgValTy->isVoidPointerType()) + if (!isGenericPointerType(ArgValTy)) return; /* Loading VoidPtr function argument */ - SVal ArgVal = State->getSVal(R, ArgValTy); + const SVal ArgVal = State->getSVal(R, ArgValTy); const MemRegion *ArgValAsRegion = ArgVal.getAsRegion(); if (!ArgValAsRegion) return; // If argument has value from caller, CharTy will not be used - SVal ArgValDeref = State->getSVal(ArgValAsRegion, ASTCtx.CharTy); + const SVal ArgValDeref = State->getSVal(ArgValAsRegion, ASTCtx.CharTy); const NoteTag *Tag; if (const auto *ArgValDerefAsRegion = ArgValDeref.getAsRegion()) { @@ -209,21 +255,10 @@ void CapabilityCopyChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, C.addTransition(State, C.getPredecessor(), Tag); } -static bool isCapabilityStorage(CheckerContext &C, const MemRegion *R, - bool AcceptUnaligned = true) { - const MemRegion *BaseReg = stripNonCapShift(R, C.getASTContext()); - if (!AcceptUnaligned && C.getState()->contains(BaseReg)) - return false; - if (const auto *SymR = dyn_cast(BaseReg)) { - QualType const Ty = SymR->getSymbol()->getType(); - if (Ty->isVoidPointerType()) - return true; - return isPointerToCapTy(Ty, C.getASTContext()); - } - return false; -} +namespace { + -static auto whileConditionMatcher() { +auto whileConditionMatcher() { using namespace clang::ast_matchers; // (--len) @@ -231,20 +266,18 @@ static auto whileConditionMatcher() { auto U = unaryOperator(hasOperatorName("--"), hasUnaryOperand(expr().bind("len"))); // (--len > 0) - auto BO = binaryOperation( - hasAnyOperatorName("!=", ">"), - hasLHS(U), - hasRHS(ignoringImplicit(integerLiteral(equals(0)))) - ); + auto BO = + binaryOperation(hasAnyOperatorName("!=", ">"), hasLHS(U), + hasRHS(ignoringImplicit(integerLiteral(equals(0))))); return stmt(anyOf(U, BO)); } -static bool isInsideSmallConstantBoundLoop(const Stmt *S, CheckerContext &C, - unsigned BlockSize) { +bool isInsideSmallConstantBoundLoop(const Stmt *S, CheckerContext &C, + unsigned BlockSize) { ASTContext &ASTCtx = C.getASTContext(); SValBuilder &SVB = C.getSValBuilder(); - unsigned CapSize = getCapabilityTypeSize(ASTCtx); - unsigned IterNum4CapCopy = CapSize / BlockSize; + const unsigned CapSize = getCapabilityTypeSize(ASTCtx); + const unsigned IterNum4CapCopy = CapSize / BlockSize; const NonLoc &ItVal = SVB.makeIntVal(IterNum4CapCopy, true); auto BoundVarList = C.getState()->get(); @@ -270,6 +303,71 @@ static bool isInsideSmallConstantBoundLoop(const Stmt *S, CheckerContext &C, return false; } +bool isAssignmentInCondition(const Stmt *S, CheckerContext &C) { + using namespace clang::ast_matchers; + auto A = binaryOperation(hasOperatorName("=")).bind("T"); + auto L = anyOf( + forStmt(hasCondition(expr(hasDescendant(A)))), + whileStmt(hasCondition(expr(hasDescendant(A)))), + doStmt(hasCondition(expr(hasDescendant(A)))) + ); + + auto LB = expr(hasAncestor(stmt(L)), equalsBoundNode("T")); + auto M = match(LB, *S, C.getASTContext()); + return !M.empty(); +} + +const MemRegion *isCapAlignCheck(const BinaryOperator *BO, CheckerContext &C) { + if (BO->getOpcode() != clang::BO_And) + return nullptr; + + const long CapAlignMask = + getCapabilityTypeAlign(C.getASTContext()).getQuantity() - 1; + + const SVal &RHSVal = C.getSVal(BO->getRHS()); + if (!RHSVal.isConstant(CapAlignMask)) + return nullptr; + + return C.getSVal(BO->getLHS()).getAsRegion(); +} + +bool handleAlignmentCheck(const BinaryOperator *BO, ProgramStateRef State, + CheckerContext &C) { + auto ExprPtrReg = isCapAlignCheck(BO, C); + if (!ExprPtrReg) + return false; + + if (!isVoidOrCharPtrArgRegion(ExprPtrReg)) + return false; + + SVal AndVal = C.getSVal(BO); + if (AndVal.isUnknown()) { + const LocationContext *LCtx = C.getLocationContext(); + AndVal = C.getSValBuilder().conjureSymbolVal(nullptr, BO, LCtx, + BO->getType(), C.blockCount()); + State = State->BindExpr(BO, LCtx, AndVal); + } + + SValBuilder &SVB = C.getSValBuilder(); + auto PtrIsCapAligned = SVB.evalEQ(State, AndVal, SVB.makeIntVal(0, true)); + ProgramStateRef Aligned, NotAligned; + std::tie(Aligned, NotAligned) = + State->assume(PtrIsCapAligned.castAs()); + + if (NotAligned) { + // If void* argument value is not capability aligned, then it cannot + // be a pointer to capability + NotAligned = NotAligned->add(ExprPtrReg->StripCasts()); + C.addTransition(NotAligned); + } + + if (Aligned) + C.addTransition(Aligned); + + return NotAligned || Aligned; +} +} //namespace + void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const { ASTContext &ASTCtx = C.getASTContext(); @@ -293,10 +391,14 @@ void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, return; // Skip if loop bound is not big enough to copy capability - unsigned BlockSize = ASTCtx.getTypeSize(PointeeTy); + const unsigned BlockSize = ASTCtx.getTypeSize(PointeeTy); if (isInsideSmallConstantBoundLoop(S, C, BlockSize)) return; + // Suppress for `while ((*dst++=src++));` + if (isAssignmentInCondition(S, C)) + return; + /* Storing capability to capability storage as non-cap*/ if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { auto W = std::make_unique( @@ -306,6 +408,108 @@ void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, } } +namespace { +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::internal; + +ProgramStateRef markOriginAsCStringForMatch(const Stmt *E, Matcher D, CheckerContext &C) { + auto M = match(D, *E, C.getASTContext()); + + bool Updated = false; + ProgramStateRef State = C.getState(); + for (auto I : M) { + auto CExpr = I.getNodeAs("c"); + const SVal &CVal = C.getSVal(CExpr); + + const MemRegion *R = CVal.getAsRegion(); + if (!R) + if (SymbolRef Sym = CVal.getAsSymbol()) + R = Sym->getOriginRegion(); + if (!R) + continue; + + const MemRegion *BaseReg = stripNonCapShift(R, C.getASTContext()); + + if (isVoidOrCharPtrArgRegion(BaseReg)) { + /* It's probably a string, so it's not to be regarded as a potential + * pointer to capability */ + State = State->add(BaseReg); + Updated = true; + } + } + return Updated ? State : nullptr; +} + + +bool checkForWhileBoundVar(const Stmt *Condition, CheckerContext &C, + ProgramStateRef PrevSt) { + ASTContext &ASTCtx = C.getASTContext(); + + using namespace clang::ast_matchers; + // TODO: do-while, merge with second matcher + auto childOfWhile = stmt(hasParent(stmt(anyOf(whileStmt(), doStmt())))); + auto L = match(childOfWhile, *Condition, ASTCtx); + if (L.empty()) + return false; + + auto whileCond = whileConditionMatcher(); + + auto M = match(whileCond, *Condition, ASTCtx); + if (M.size() != 1) + return false; + + const SVal &CondVal = C.getSVal(Condition); + if (C.getSVal(Condition).isUnknownOrUndef()) + return false; + + ProgramStateRef ThenSt, ElseSt; + std::tie(ThenSt, ElseSt) = + PrevSt->assume(CondVal.castAs()); + for (auto I : M) { + auto L = I.getNodeAs("len"); + if (SymbolRef ISym = C.getSVal(L).getAsSymbol()) { + if (ThenSt) + ThenSt = ThenSt->add(ISym); + if (ElseSt) + ElseSt = ElseSt->set(llvm::ImmutableList()); + } else + return false; + } + if (ThenSt) + C.addTransition(ThenSt); + if (ElseSt) + C.addTransition(ElseSt); + return true; +} +} // namespace + +void CapabilityCopyChecker::checkBranchCondition(const Stmt *Cond, + CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + + auto CharMatcher = expr(hasType(isAnyCharacter())).bind("c"); + auto CondMatcher = stmt(expr(ignoringParenCasts(CharMatcher))); + const ProgramStateRef N = markOriginAsCStringForMatch(Cond, CondMatcher, C); + bool updated = checkForWhileBoundVar(Cond, C, N ? N : C.getState()); + if (!updated && N) + C.addTransition(N); +} + +void CapabilityCopyChecker::checkPostStmt(const ArraySubscriptExpr *E, + CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + + const Expr *Index = E->getIdx(); + auto CharMatcher = expr(hasType(isAnyCharacter())).bind("c"); + auto IndexMatcher = stmt(expr(ignoringParenCasts(CharMatcher))); + + const ProgramStateRef N = markOriginAsCStringForMatch(Index, IndexMatcher, C); + if (N) + C.addTransition(N); +} + ExplodedNode *CapabilityCopyChecker::checkBinaryOpArg(CheckerContext &C, const Expr* E) const { ASTContext &ASTCtx = C.getASTContext(); @@ -347,110 +551,62 @@ ExplodedNode *CapabilityCopyChecker::checkBinaryOpArg(CheckerContext &C, return nullptr; } -static const MemRegion *isCapAlignCheck(const BinaryOperator *BO, CheckerContext &C) { - if (BO->getOpcode() != clang::BO_And) - return nullptr; - - long CapAlignMask = - getCapabilityTypeAlign(C.getASTContext()).getQuantity() - 1; - - const SVal &RHSVal = C.getSVal(BO->getRHS()); - if (!RHSVal.isConstant(CapAlignMask)) - return nullptr; - - return C.getSVal(BO->getLHS()).getAsRegion(); -} - void CapabilityCopyChecker::checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const { - ASTContext &ASTCtx = C.getASTContext(); + const ASTContext &ASTCtx = C.getASTContext(); if (!isPureCapMode(ASTCtx)) return; - ExplodedNode *N = nullptr; - /* Check for capability repr bytes used in arithmetic */ + ExplodedNode *N = nullptr; if (!BO->isAssignmentOp() || BO->isCompoundAssignmentOp()) { - N = checkBinaryOpArg(C, BO->getLHS()); - if (!N) + if (!(N = checkBinaryOpArg(C, BO->getLHS()))) N = checkBinaryOpArg(C, BO->getRHS()); } if (!N) N = C.getPredecessor(); + ProgramStateRef State = N->getState(); - /* Handle alignment check */ - if (auto ExprPtrReg = isCapAlignCheck(BO, C)) { - if (!isVoidPtrArgRegion(ExprPtrReg)) - return; - ProgramStateRef State = N->getState(); - - SVal AndVal = C.getSVal(BO); - if (AndVal.isUnknown()) { - const LocationContext *LCtx = C.getLocationContext(); - AndVal = C.getSValBuilder().conjureSymbolVal( - nullptr, BO, LCtx, BO->getType(), C.blockCount()); - State = State->BindExpr(BO, LCtx, AndVal); - } - - SValBuilder &SVB = C.getSValBuilder(); - auto PtrIsCapAligned = SVB.evalEQ(State, AndVal, SVB.makeIntVal(0, true)); - ProgramStateRef Aligned, NotAligned; - std::tie(Aligned, NotAligned) = - State->assume(PtrIsCapAligned.castAs()); - - if (NotAligned) { - // If void* argument value is not capability aligned, then it cannot - // be a pointer to capability - NotAligned = NotAligned->add(ExprPtrReg->StripCasts()); - C.addTransition(NotAligned); - } - - if (Aligned) - C.addTransition(Aligned); - } - -} - -void CapabilityCopyChecker::checkBranchCondition(const Stmt *Condition, - CheckerContext &C) const { - ASTContext &ASTCtx = C.getASTContext(); - if (!isPureCapMode(ASTCtx)) - return; - + /* CString character check */ using namespace clang::ast_matchers; - // TODO: do-while, merge with second matcher - auto childOfWhile = stmt(hasParent(stmt(anyOf(whileStmt(), doStmt())))); - auto L = match(childOfWhile, *Condition, ASTCtx); - if (L.empty()) - return; - - auto whileCond = whileConditionMatcher(); + auto CharMatcher = ignoringParenCasts(expr(hasType(isAnyCharacter())).bind("c")); + auto Cmp = binaryOperation(isComparisonOperator(), has(CharMatcher)); + + /* Not a c-string, but rather an array of individual bytes. + * We shall handle it the same way as c-string */ + auto BitOps = hasAnyOperatorName("&", "&=", "^", "^=", "|", "|="); + auto Arithm = binaryOperation(BitOps, has(CharMatcher)); + + auto BOM = anyOf(Cmp, Arithm); + const ProgramStateRef NewState = markOriginAsCStringForMatch(BO, BOM, C); + if (NewState) + State = NewState; + + bool updated = handleAlignmentCheck(BO, State, C); + if (!updated && NewState) + C.addTransition(NewState); +} - auto M = match(whileCond, *Condition, ASTCtx); - if (M.size() != 1) +void CapabilityCopyChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (!Call.isGlobalCFunction()) return; - const SVal &CondVal = C.getSVal(Condition); - if (C.getSVal(Condition).isUnknownOrUndef()) + const int *StrParams = CStringFn.lookup(Call); + if (!StrParams) return; - ProgramStateRef ThenSt, ElseSt; - std::tie(ThenSt, ElseSt) = - C.getState()->assume(CondVal.castAs()); - for (auto I : M) { - auto L = I.getNodeAs("len"); - if (SymbolRef ISym = C.getSVal(L).getAsSymbol()) { - if (ThenSt) - ThenSt = ThenSt->add(ISym); - if (ElseSt) - ElseSt = ElseSt->set(llvm::ImmutableList()); - } else - return; + ProgramStateRef State = C.getState(); + int i = 0, P = *StrParams; + while (P) { + if (P & 1) + if (const MemRegion *Str = Call.getArgSVal(i).getAsRegion()) + State = State->add(Str); + i++; + P >>= 1; } - if (ThenSt) - C.addTransition(ThenSt); - if (ElseSt) - C.addTransition(ElseSt); + + C.addTransition(State); } void ento::registerCapabilityCopyChecker(CheckerManager &mgr) { diff --git a/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c b/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c index fe6ed24301c3..716ae8428243 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c @@ -94,6 +94,39 @@ void memcpy_impl_bad(void* src0, void *dst0, size_t len) { *dst++ = *src++; // expected-warning{{Tag-stripping store of a capability}} } +void charptr(char* src, char *dst) { + *dst++ = *src++; // expected-warning{{Tag-stripping store of a capability}} +} + +#define EOL 10 +void c_string(char* src1, char* src2, char* src3, char *src4, char *dst) { + int i = 0; + while (src1[i]) + *dst++ = src1[i++]; // no warning + + src2++; + while (*src2 != '.') + *dst++ = *src2++; // no warning + + char c; + while ((c = *src3++)) + *dst++ = c; // no warning + + while (EOL != (*dst++ = *src4++)); // no warning +} + +struct S { + int x; + int *p; +}; + +void struct_field(void *p) { + struct S *ps = p; + ps->p = malloc(10*sizeof(int)); + int x = ps->x; + *ps->p = x; // no warning +} + char voidptr_arg_load1(void *q) { char *s = (char*)q; return *s; @@ -115,6 +148,22 @@ char fp_init_str(void) { return s[3]; } +extern size_t strlen(const char *s); +void strcpy_impl(char* src, char *dst) { + size_t len = strlen(src); + for (int i=0; i < len; i++) + *dst++ = *src++; // no warning +} + +extern const unsigned short int **__ctype_b_loc (void); +#define isalpha(c) \ + ((*__ctype_b_loc ())[(int) ((c))] & (unsigned short int) (1 << 10)) +void ctype(char* src, char *dst, int len) { + if (isalpha(src[0])) + while (--len) + *dst++ = *src++; // no warning +} + // ===== Part of capability representation used as argument in binary operator ===== int hash_no_call(void *key0, size_t len) { From ca199f21026fd729d51bb65155557bb15916f262 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 14 Jul 2023 14:14:37 +0100 Subject: [PATCH 13/77] [CHERI-CSA] CapabilityCopyChecker: suppress FP for short copies --- .../Checkers/CHERI/CapabilityCopyChecker.cpp | 51 +++++++++--- .../Checkers/CHERI/capability-copy-purecap.c | 80 +++++++++---------- 2 files changed, 77 insertions(+), 54 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index 0196c808c59c..379851936cf6 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -189,8 +189,8 @@ CHERITagState getTagState(SVal Val, CheckerContext &C, return CHERITagState::getUnknown(); } -unsigned getCapabilityTypeSize(ASTContext &ASTCtx) { - return ASTCtx.getTypeSize(ASTCtx.VoidPtrTy); +CharUnits getCapabilityTypeSize(ASTContext &ASTCtx) { + return ASTCtx.getTypeSizeInChars(ASTCtx.VoidPtrTy); } CharUnits getCapabilityTypeAlign(ASTContext &ASTCtx) { @@ -272,13 +272,9 @@ auto whileConditionMatcher() { return stmt(anyOf(U, BO)); } -bool isInsideSmallConstantBoundLoop(const Stmt *S, CheckerContext &C, - unsigned BlockSize) { - ASTContext &ASTCtx = C.getASTContext(); +bool isInsideSmallConstantBoundLoop(const Stmt *S, CheckerContext &C, long N) { SValBuilder &SVB = C.getSValBuilder(); - const unsigned CapSize = getCapabilityTypeSize(ASTCtx); - const unsigned IterNum4CapCopy = CapSize / BlockSize; - const NonLoc &ItVal = SVB.makeIntVal(IterNum4CapCopy, true); + const NonLoc &ItVal = SVB.makeIntVal(N, true); auto BoundVarList = C.getState()->get(); for (auto &&V : BoundVarList) { @@ -303,6 +299,41 @@ bool isInsideSmallConstantBoundLoop(const Stmt *S, CheckerContext &C, return false; } +CharUnits getNonCapShift(SVal V) { + if (SymbolRef Sym = V.getAsSymbol()) + if (const MemRegion *MR = Sym->getOriginRegion()) + if (const auto *ER = dyn_cast(MR)) { + const RegionRawOffset &Offset = ER->getAsArrayOffset(); + if (Offset.getRegion()) + return Offset.getOffset(); + } + + return CharUnits::Zero(); +} + +bool isPartOfCopyingSequence(const QualType &CopyTy, SVal V, const Stmt *S, + CheckerContext &C) { + ASTContext &ASTCtx = C.getASTContext(); + const CharUnits CopySize = ASTCtx.getTypeSizeInChars(CopyTy); + const CharUnits CapSize = getCapabilityTypeSize(ASTCtx); + + /* False if loop bound is not big enough to copy capability */ + const long N = CapSize.alignTo(CopySize) / CopySize; + if (isInsideSmallConstantBoundLoop(S, C, N)) + return false; + + /* True if copy is in loop */ + using namespace clang::ast_matchers; + auto L = anyOf(forStmt(), whileStmt(), doStmt()); + auto LB = expr(hasAncestor(stmt(L))); + if (!match(LB, *S, C.getASTContext()).empty()) + return true; + + /* Sequence of individual assignments */ + CharUnits Shift = getNonCapShift(V); + return (Shift + CopySize) >= CapSize; +} + bool isAssignmentInCondition(const Stmt *S, CheckerContext &C) { using namespace clang::ast_matchers; auto A = binaryOperation(hasOperatorName("=")).bind("T"); @@ -390,9 +421,7 @@ void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, if (!isCapabilityStorage(C, MR)) return; - // Skip if loop bound is not big enough to copy capability - const unsigned BlockSize = ASTCtx.getTypeSize(PointeeTy); - if (isInsideSmallConstantBoundLoop(S, C, BlockSize)) + if (ValTag.mayBeTagged() && !isPartOfCopyingSequence(PointeeTy, V, S, C)) return; // Suppress for `while ((*dst++=src++));` diff --git a/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c b/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c index 716ae8428243..f7a23623cde1 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c @@ -44,6 +44,8 @@ void copy_intptr(int **ppy) { *ppy = *v; // no-warning: copy as int* } +// ===== Pointer to capability passed as void* ==== + static void swapfunc(void *a, void *b, int n) { long i = n; char *pi = (char *)(a); @@ -55,18 +57,6 @@ static void swapfunc(void *a, void *b, int n) { } while (--i > 0); } -void *realloc_impl(void *ptr, size_t size) { - void *dst = malloc(size); - if (size <= sizeof(size)) { - size_t *mcsrc = (size_t *)(ptr); - size_t *mcdst = (size_t *)(dst); - *mcdst = *mcsrc; // expected-warning{{Tag-stripping store of a capability}} - } else - memmove(dst, ptr, size); - free(ptr); - return dst; -} - void memcpy_impl_good(void* src0, void *dst0, size_t len) { char *src = src0; char *dst = dst0; @@ -94,29 +84,9 @@ void memcpy_impl_bad(void* src0, void *dst0, size_t len) { *dst++ = *src++; // expected-warning{{Tag-stripping store of a capability}} } -void charptr(char* src, char *dst) { - *dst++ = *src++; // expected-warning{{Tag-stripping store of a capability}} -} - -#define EOL 10 -void c_string(char* src1, char* src2, char* src3, char *src4, char *dst) { - int i = 0; - while (src1[i]) - *dst++ = src1[i++]; // no warning - - src2++; - while (*src2 != '.') - *dst++ = *src2++; // no warning - - char c; - while ((c = *src3++)) - *dst++ = c; // no warning - - while (EOL != (*dst++ = *src4++)); // no warning -} - struct S { int x; + int fill[100]; int *p; }; @@ -127,16 +97,6 @@ void struct_field(void *p) { *ps->p = x; // no warning } -char voidptr_arg_load1(void *q) { - char *s = (char*)q; - return *s; -} - -void voidptr_arg_store1(void *q) { - char *s = (char*)q; - *s = 42; -} - char fp_malloc() { void *q = malloc(100); char *s = (char*)q; // no warning @@ -148,6 +108,40 @@ char fp_init_str(void) { return s[3]; } +void copyAsLong(void* src0, void *dst0, size_t len) { + if (len == 16) { + long *src = src0; + long *dst = dst0; + *dst++ = *src++; + *dst = *src; // expected-warning{{Tag-stripping store of a capability}} + } +} + +// ===== Pointer to capability passed as char* ==== + +void char_ptr(char* src, char *dst, size_t len) { + while (--len) + *dst++ = *src++; // expected-warning{{Tag-stripping store of a capability}} +} + +#define EOL 10 +void c_string(char* src1, char* src2, char* src3, char *src4, char *dst) { + int i = 0; + while (src1[i]) + *dst++ = src1[i++]; // no warning + + src2++; + while (*src2 != '.') + *dst++ = *src2++; // no warning + + char c; + while ((c = *src3++)) + *dst++ = c; // no warning + + while (EOL != (*dst++ = *src4++)); // no warning +} + + extern size_t strlen(const char *s); void strcpy_impl(char* src, char *dst) { size_t len = strlen(src); From 58d95a937197b4d910b1c135886ae84fd997dabc Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 14 Jul 2023 15:32:45 +0100 Subject: [PATCH 14/77] [CHERI-CSA] CapabilityCopyChecker: improve bug trace --- .../Checkers/CHERI/CapabilityCopyChecker.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index 379851936cf6..3d6e86c830ef 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -243,15 +243,26 @@ void CapabilityCopyChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, // If argument has value from caller, CharTy will not be used const SVal ArgValDeref = State->getSVal(ArgValAsRegion, ASTCtx.CharTy); - const NoteTag *Tag; + bool T; if (const auto *ArgValDerefAsRegion = ArgValDeref.getAsRegion()) { - Tag = C.getNoteTag("void* argument points to capability"); State = State->add(ArgValDerefAsRegion); + T = true; } else if (ArgValDeref.getAsSymbol()) { - Tag = C.getNoteTag("void* argument may be a pointer to capability"); + T = false; } else return; + const NoteTag *Tag = + C.getNoteTag([T, ArgValTy](PathSensitiveBugReport &BR) -> std::string { + SmallString<80> Msg; + llvm::raw_svector_ostream OS(Msg); + OS << ArgValTy.getAsString(); + if (T) + OS << " argument points to capability"; + else + OS << " argument may be a pointer to capability"; + return std::string(OS.str()); + }); C.addTransition(State, C.getPredecessor(), Tag); } From 41cbcb76f4a3065dd15b611678519d7c3cd05028 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 24 Jul 2023 18:31:53 +0100 Subject: [PATCH 15/77] [CHERI_CSA] ProvenanceSourceChecker: silence for hybrid mode --- .../Checkers/CHERI/CHERIUtils.cpp | 27 +++++++++++++++++++ .../Checkers/CHERI/CHERIUtils.h | 26 ++++++++++++++++++ .../Checkers/CHERI/CapabilityCopyChecker.cpp | 11 ++------ .../CHERI/ProvenanceSourceChecker.cpp | 14 ++++++++++ .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + 5 files changed, 70 insertions(+), 9 deletions(-) create mode 100644 clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp create mode 100644 clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp new file mode 100644 index 000000000000..70cd5d71053e --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp @@ -0,0 +1,27 @@ +//===-- CHERIUtils.h -------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#include "CHERIUtils.h" + +namespace clang { +namespace ento { +namespace cheri { + +bool isPureCapMode(const ASTContext &C) { + return C.getTargetInfo().areAllPointersCapabilities(); +} + +bool isPointerToCapTy(const QualType Type, ASTContext &Ctx) { + if (!Type->isPointerType()) + return false; + return Type->getPointeeType()->isCHERICapabilityType(Ctx, true); +} + +} // end of namespace: cheri +} // end of namespace: ento +} // end of namespace: clang \ No newline at end of file diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h new file mode 100644 index 000000000000..a8ddd0a322d4 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h @@ -0,0 +1,26 @@ +//===-- CHERIUtils.h -------------------------------------------*- 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_CHERI_CHERIUTILS_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_CHERI_CHERIUTILS_H + +#include "clang/StaticAnalyzer/Core/Checker.h" + +namespace clang { +namespace ento { +namespace cheri { + +bool isPureCapMode(const ASTContext &C); + +bool isPointerToCapTy(const QualType Type, ASTContext &Ctx); + +} // end of namespace: cheri +} // end of namespace: ento +} // end of namespace: clang + +#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_CHERI_CHERIUTILS_H diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index 3d6e86c830ef..f96902cb06dd 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// +#include "CHERIUtils.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" @@ -23,6 +24,7 @@ using namespace clang; using namespace ento; +using namespace cheri; namespace { @@ -104,15 +106,6 @@ CapabilityCopyChecker::CapabilityCopyChecker() { } namespace { -bool isPureCapMode(const ASTContext &C) { - return C.getTargetInfo().areAllPointersCapabilities(); -} - -bool isPointerToCapTy(const QualType Type, ASTContext &Ctx) { - if (!Type->isPointerType()) - return false; - return Type->getPointeeType()->isCHERICapabilityType(Ctx, true); -} bool isNonCapScalarType(QualType T, ASTContext &C) { if (!T->isScalarType()) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index 8c7abdbb8ba4..6027785e42bb 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// +#include "CHERIUtils.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include #include "clang/StaticAnalyzer/Core/Checker.h" @@ -19,6 +20,7 @@ using namespace clang; using namespace ento; +using namespace cheri; namespace { class ProvenanceSourceChecker : public Checker, @@ -134,6 +136,9 @@ static bool isPointerToIntCapCast(const CastExpr *CE) { void ProvenanceSourceChecker::checkPostStmt(const CastExpr *CE, CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + if (isIntegerToIntCapCast(CE)) { SymbolRef IntCapSym = C.getSVal(CE).getAsSymbol(); if (!IntCapSym) @@ -206,6 +211,9 @@ static bool isIntToVoidPtrCast(const CastExpr *CE) { // Report intcap with ambiguous or NULL-derived provenance cast to pointer void ProvenanceSourceChecker::checkPreStmt(const CastExpr *CE, CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + if (CE->getCastKind() != clang::CK_IntegralToPointer) return; @@ -366,6 +374,9 @@ void ProvenanceSourceChecker::propagateProvenanceInfoForBinOp( // store them to report again if used as pointer void ProvenanceSourceChecker::checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + BinaryOperatorKind const OpCode = BO->getOpcode(); if (!(BinaryOperator::isAdditiveOp(OpCode) || BinaryOperator::isMultiplicativeOp(OpCode) @@ -421,6 +432,9 @@ void ProvenanceSourceChecker::checkPostStmt(const BinaryOperator *BO, void ProvenanceSourceChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + ProgramStateRef State = C.getState(); bool Removed = false; diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index dcda2b092066..c63a43b9acfa 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -24,6 +24,7 @@ add_clang_library(clangStaticAnalyzerCheckers CheckSecuritySyntaxOnly.cpp CheckSizeofPointer.cpp CheckerDocumentation.cpp + CHERI/CHERIUtils.cpp CHERI/ProvenanceSourceChecker.cpp CHERI/CapabilityCopyChecker.cpp ChrootChecker.cpp From cef342e63a73be520e5f6a6076022dd45e6ca0c4 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 26 Jul 2023 21:02:07 +0100 Subject: [PATCH 16/77] [CHERI_CSA] CHERIUtils --- .../lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h | 6 ++++++ .../Checkers/CHERI/CapabilityCopyChecker.cpp | 13 ------------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h index a8ddd0a322d4..b530805d81b2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h @@ -19,6 +19,12 @@ bool isPureCapMode(const ASTContext &C); bool isPointerToCapTy(const QualType Type, ASTContext &Ctx); +CharUnits getCapabilityTypeSize(ASTContext &ASTCtx); + +CharUnits getCapabilityTypeAlign(ASTContext &ASTCtx); + +bool isGenericPointerType(const QualType T); + } // end of namespace: cheri } // end of namespace: ento } // end of namespace: clang diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index f96902cb06dd..4aeb6c3e9be6 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -126,11 +126,6 @@ const MemRegion *stripNonCapShift(const MemRegion *R, ASTContext &ASTCtx) { return ER->getSuperRegion(); } -bool isGenericPointerType(const QualType T) { - return T->isVoidPointerType() || - (T->isPointerType() && T->getPointeeType()->isCharType()); -} - bool isVoidOrCharPtrArgRegion(const MemRegion *Reg) { if (!Reg) return false; @@ -182,14 +177,6 @@ CHERITagState getTagState(SVal Val, CheckerContext &C, return CHERITagState::getUnknown(); } -CharUnits getCapabilityTypeSize(ASTContext &ASTCtx) { - return ASTCtx.getTypeSizeInChars(ASTCtx.VoidPtrTy); -} - -CharUnits getCapabilityTypeAlign(ASTContext &ASTCtx) { - return ASTCtx.getTypeAlignInChars(ASTCtx.VoidPtrTy); -} - bool isCapabilityStorage(CheckerContext &C, const MemRegion *R, bool AcceptUnaligned = true) { const MemRegion *BaseReg = stripNonCapShift(R, C.getASTContext()); From b8f1f00b34bebdb0856dfe4010a995694e269a90 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 26 Jul 2023 21:02:54 +0100 Subject: [PATCH 17/77] [CHERI_CSA] Add Capability Alignment Checker --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../Checkers/CHERI/CHERIUtils.cpp | 13 + .../CHERI/CapabilityAlignmentChecker.cpp | 318 ++++++++++++++++++ .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../Checkers/CHERI/capability-alignment.c | 24 ++ 5 files changed, 360 insertions(+) create mode 100644 clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp create mode 100644 clang/test/Analysis/Checkers/CHERI/capability-alignment.c diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 60feaeb9e52b..dd07e613747f 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1756,4 +1756,8 @@ def CapabilityCopyChecker : Checker<"CapabilityCopyChecker">, HelpText<"Check tag-stripping memory copy.">, Documentation; +def CapabilityAlignmentChecker : Checker<"CapabilityAlignmentChecker">, + HelpText<"Check underaligned pointers.">, + Documentation; + } // end alpha.cheri diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp index 70cd5d71053e..f338e988984d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp @@ -22,6 +22,19 @@ bool isPointerToCapTy(const QualType Type, ASTContext &Ctx) { return Type->getPointeeType()->isCHERICapabilityType(Ctx, true); } +CharUnits getCapabilityTypeSize(ASTContext &ASTCtx) { + return ASTCtx.getTypeSizeInChars(ASTCtx.VoidPtrTy); +} + +CharUnits getCapabilityTypeAlign(ASTContext &ASTCtx) { + return ASTCtx.getTypeAlignInChars(ASTCtx.VoidPtrTy); +} + +bool isGenericPointerType(const QualType T) { + return T->isVoidPointerType() || + (T->isPointerType() && T->getPointeeType()->isCharType()); +} + } // end of namespace: cheri } // end of namespace: ento } // end of namespace: clang \ No newline at end of file diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp new file mode 100644 index 000000000000..62df9361ed55 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp @@ -0,0 +1,318 @@ +//=== CapabilityAlignmentChecker.cpp - Capability Alignment Checker -*- 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 +// +//===----------------------------------------------------------------------===// +// +// Tracks pointer values alignment and reports pointer casts of underaligned +// values to types with strict alignment requirements. +// * Cast Align Bug +// Cast of underaligned pointer value to the pointer type with stricter +// alignment requirements. +// * CapCastAlignBug +// Special case for CHERI: casts to pointer to capability. Storing +// capabilities at not capability-aligned addressed will result in +// stored capability losing its tag. +// +// Currently, this checker (deliberately) does not take into account: +// - alignment of static and automatic allocations, enforced by ABI (other +// than implied by variable types), +// - alignment of fields that is guaranteed implicitly by the alignment of +// the whole object and current field offset. +// Relying on this could make the code less portable and easier to break, but +// it may be a good idea to introduce a separate warning type that will not +// be reported for pointer values properly aligned due to the reasons above, +// so that it will produce just a few reports for critical bugs only. +// +//===----------------------------------------------------------------------===// + +#include "CHERIUtils.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include +#include +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; +using namespace cheri; + +namespace { +class CapabilityAlignmentChecker + : public Checker, check::PostStmt, + check::PostStmt> { + std::unique_ptr CastAlignBug; + + +public: + CapabilityAlignmentChecker(); + + void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; + void checkPostStmt(const CastExpr *BO, CheckerContext &C) const; + void checkPreStmt(const CastExpr *BO, CheckerContext &C) const; +}; + +} // namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(TrailingZerosMap, SymbolRef, int) + +CapabilityAlignmentChecker::CapabilityAlignmentChecker() { + CastAlignBug.reset( + new BugType(this, "Cast increases required alignment", "CHERI portability")); +} + +namespace { + +int getTrailingZerosCount(const SVal &V, CheckerContext &C); + +int getTrailingZerosCount(SymbolRef Sym, CheckerContext &C) { + const int *Align = C.getState()->get(Sym); + if (Align) + return *Align; + return -1; +} + +int getTrailingZerosCount(const MemRegion *R, CheckerContext &C) { + R = R->StripCasts(); + + if (const ElementRegion *ER = R->getAs()) { + const MemRegion *Base = ER->getBaseRegion(); + int BaseTZC = -1; + if (const SymbolicRegion *SymbBase = Base->getSymbolicBase()) + BaseTZC = getTrailingZerosCount(SymbBase->getSymbol(), C); + else + BaseTZC = getTrailingZerosCount(Base, C); + int IdxTZC = getTrailingZerosCount(ER->getIndex(), C); + if (BaseTZC >=0 && IdxTZC >= 0) + return std::min(BaseTZC, IdxTZC); + } + + if (R->getSymbolicBase()) + return -1; + + const TypedValueRegion *TR = R->getAs(); + if (!TR) + return -1; + + ASTContext &ASTCtx = C.getASTContext(); + const QualType &PT = TR->getDesugaredValueType(ASTCtx); + unsigned A = ASTCtx.getTypeAlignInChars(PT).getQuantity(); + return llvm::APSInt::getUnsigned(A).countTrailingZeros(); +} + +int getTrailingZerosCount(const SVal &V, CheckerContext &C) { + if (V.isUnknownOrUndef()) + return -1; + + if (V.isConstant()) { + if (Optional LV = V.getAs()) + return LV->getValue().countTrailingZeros(); + if (Optional NV = V.getAs()) + return NV->getValue().countTrailingZeros(); + return -1; + } + + if (SymbolRef Sym = V.getAsSymbol()) { + int Res = getTrailingZerosCount(Sym, C); + if (Res >=0) + return Res; + } + + if (const MemRegion *R = V.getAsRegion()) { + return getTrailingZerosCount(R, C); + } + + return -1; +} + +int getTrailingZerosCount(const Expr *E, CheckerContext &C) { + const SVal &V = C.getSVal(E); + return getTrailingZerosCount(V, C); +} + +} // namespace + +void CapabilityAlignmentChecker::checkPreStmt(const CastExpr *CE, + CheckerContext &C) const { + if (CE->getCastKind() != CastKind::CK_BitCast) + return; + + const Expr *Src = CE->getSubExpr(); + const QualType &DstType = CE->getType(); + if (!Src->getType()->isPointerType() || !DstType->isPointerType()) + return; + + ASTContext &ASTCtx = C.getASTContext(); + int SrcTZC = getTrailingZerosCount(CE->getSubExpr(), C); + if (SrcTZC < 0) + return; + if ((unsigned)SrcTZC >= sizeof(unsigned int)*8) + return; // Too aligned, probably a Zero + unsigned SrcAlign = (1U << SrcTZC); + const QualType &DstPointeeTy = DstType->getPointeeType(); + unsigned DstReqAlign = ASTCtx.getTypeAlignInChars(DstPointeeTy).getQuantity(); + if (SrcAlign < DstReqAlign) { + if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { + SmallString<350> ErrorMessage; + llvm::raw_svector_ostream OS(ErrorMessage); + OS << "Cast increases required alignment: "; + OS << SrcAlign; + OS << " -> "; + OS << DstReqAlign; + OS << ")"; + auto W = std::make_unique( + *CastAlignBug, ErrorMessage, ErrNode); + W->addRange(CE->getSourceRange()); + C.emitReport(std::move(W)); + } + } +} + +void printAlign(raw_ostream &OS, unsigned TZC) { + OS << "aligned("; + if (TZC < sizeof(unsigned long)*8) + OS << (1LU << TZC); + else + OS << "2^(" << TZC << ")"; + OS << ")"; +} + +void CapabilityAlignmentChecker::checkPostStmt(const CastExpr *CE, + CheckerContext &C) const { + CastKind CK = CE->getCastKind(); + if (CK != CastKind::CK_BitCast && CK != CK_PointerToIntegral && + CK != CK_IntegralToPointer && CK != CK_LValueToRValue) + return; + + int DstTZC = getTrailingZerosCount(CE, C); + int SrcTZC = getTrailingZerosCount(CE->getSubExpr(), C); + + if (DstTZC < SrcTZC) { + SVal DstVal = C.getSVal(CE); + ProgramStateRef State = C.getState(); + if (DstVal.isUnknown()) { + const LocationContext *LCtx = C.getLocationContext(); + DstVal = C.getSValBuilder().conjureSymbolVal( + nullptr, CE, LCtx, CE->getType(), C.blockCount()); + State = State->BindExpr(CE, LCtx, DstVal); + } + if (SymbolRef Sym = DstVal.getAsSymbol()) { + State = State->set(Sym, SrcTZC); + const NoteTag *Tag = + C.getNoteTag([SrcTZC](PathSensitiveBugReport &BR) -> std::string { + SmallString<80> Msg; + llvm::raw_svector_ostream OS(Msg); + OS << "Alignment: "; + printAlign(OS, SrcTZC); + return std::string(OS.str()); + }); + C.addTransition(State, C.getPredecessor(), Tag); + } + } +} + +void CapabilityAlignmentChecker::checkPostStmt(const BinaryOperator *BO, + CheckerContext &C) const { + int LeftTZ = getTrailingZerosCount(BO->getLHS(), C); + if (LeftTZ < 0) + return; + int RightTZ = getTrailingZerosCount(BO->getRHS(), C); + if (RightTZ < 0 && !BO->isShiftOp() && !BO->isShiftAssignOp()) + return; + + ProgramStateRef State = C.getState(); + SVal ResVal = C.getSVal(BO); + if (ResVal.isUnknown()) { + const LocationContext *LCtx = C.getLocationContext(); + ResVal = C.getSValBuilder().conjureSymbolVal( + nullptr, BO, LCtx, BO->getType(), C.blockCount()); + State = State->BindExpr(BO, LCtx, ResVal); + } + + SymbolRef ResSymb = ResVal.getAsSymbol(); + if (!ResSymb) + return; + + + const SVal &RHSVal = C.getSVal(BO->getRHS()); + int BitWidth = C.getASTContext().getTypeSize(BO->getType()); + int Res = 0; + int RHSConst = 0; + BinaryOperator::Opcode OpCode = BO->getOpcode(); + switch (OpCode) { + case clang::BO_And: + case clang::BO_AndAssign: + Res = std::max(LeftTZ, RightTZ); + break; + case clang::BO_Or: + case clang::BO_OrAssign: + Res = std::min(LeftTZ, RightTZ); + break; + case clang::BO_Add: + case clang::BO_AddAssign: + case clang::BO_Sub: + case clang::BO_SubAssign: + if (BO->getLHS()->getType()->isPointerType()) { + const QualType &PointeeTy = BO->getLHS()->getType()->getPointeeType(); + const CharUnits A = C.getASTContext().getTypeAlignInChars(PointeeTy); + RightTZ += + llvm::APSInt::getUnsigned(A.getQuantity()).countTrailingZeros(); + } + Res = std::min(LeftTZ, RightTZ); + break; + case clang::BO_Mul: + case clang::BO_MulAssign: + Res = LeftTZ + RightTZ; + break; + case clang::BO_Div: + case clang::BO_DivAssign: + Res = LeftTZ - RightTZ; + break; + case clang::BO_Shl: + case clang::BO_ShlAssign: + case clang::BO_Shr: + case clang::BO_ShrAssign: + if (RHSVal.isConstant()) { + if (Optional NV = RHSVal.getAs()) + RHSConst = NV.getValue().getValue().getExtValue(); + } + if (BO->getOpcode() == BO_Shl || BO->getOpcode() == BO_ShlAssign) + Res = LeftTZ + RHSConst; + else + Res = RHSVal.isConstant() ? LeftTZ - RHSConst : BitWidth; + break; + default: + return; + } + + if (Res <= 0) + Res = 0; + if (Res > BitWidth) + Res = BitWidth; + + State = State->set(ResSymb, Res); + const NoteTag *Tag = + C.getNoteTag([LeftTZ, RightTZ, Res, OpCode] + (PathSensitiveBugReport &BR) -> std::string { + SmallString<80> Msg; + llvm::raw_svector_ostream OS(Msg); + OS << "Alignment: "; + printAlign(OS, LeftTZ); + OS << " " << BinaryOperator::getOpcodeStr(OpCode) << " "; + printAlign(OS, RightTZ); + OS << " => "; + printAlign(OS, Res); + return std::string(OS.str()); + }); + C.addTransition(State, C.getPredecessor(), Tag); +} + +void ento::registerCapabilityAlignmentChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterCapabilityAlignmentChecker(const CheckerManager &Mgr) { + return true; +} \ No newline at end of file diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index c63a43b9acfa..baa089ec96e7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -27,6 +27,7 @@ add_clang_library(clangStaticAnalyzerCheckers CHERI/CHERIUtils.cpp CHERI/ProvenanceSourceChecker.cpp CHERI/CapabilityCopyChecker.cpp + CHERI/CapabilityAlignmentChecker.cpp ChrootChecker.cpp CloneChecker.cpp ContainerModeling.cpp diff --git a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c new file mode 100644 index 000000000000..bd375aead4e1 --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c @@ -0,0 +1,24 @@ +// RUN: %cheri_purecap_cc1 -analyze -verify %s \ +// RUN: -analyzer-checker=core,alpha.cheri.CapabilityAlignmentChecker + +typedef __uintcap_t uintptr_t; + +double a[2048], *next = a; + +#define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) + +#define NULL (void*)0 +uintptr_t *u; + +void foo(void *v) { + char *p0 = (char*)a; + *(void**)p0 = v; // expected-warning{{Cast increases required alignment: 8 -> 16}} + char *p1 = (char*)roundup2((uintptr_t)p0, sizeof(void*)); + *(void**)p1 = v; // no warn + char *p2 = p1 + 5*sizeof(double); + *(void**)p2 = v; // expected-warning{{Cast increases required alignment: 8 -> 16}} + + if (u == NULL) + return; // no warning +} + From ea4adbba9a5eedf13479a5feff5d8fe4763d5e46 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 4 Aug 2023 15:55:29 +0100 Subject: [PATCH 18/77] [CHERI_CSA] CapabilityAlignmentChecker: assume align on parameters and globals --- .../CHERI/CapabilityAlignmentChecker.cpp | 33 ++++++++++++++++--- .../Checkers/CHERI/capability-alignment.c | 14 +++++--- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp index 62df9361ed55..346f811c4c68 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp @@ -71,6 +71,21 @@ int getTrailingZerosCount(SymbolRef Sym, CheckerContext &C) { const int *Align = C.getState()->get(Sym); if (Align) return *Align; + + // Is function argument or global? + if (const MemRegion *BaseRegOrigin = Sym->getOriginRegion()) + if (BaseRegOrigin->getMemorySpace()->hasGlobalsOrParametersStorage()) { + const QualType &SymTy = Sym->getType(); + if (SymTy->isPointerType() && !isGenericPointerType(SymTy)) { + ASTContext &ASTCtx = C.getASTContext(); + const QualType &PT = SymTy->getPointeeType(); + if (!PT->isIncompleteType()) { + unsigned A = ASTCtx.getTypeAlignInChars(PT).getQuantity(); + return llvm::APSInt::getUnsigned(A).countTrailingZeros(); + } + } + } + return -1; } @@ -97,7 +112,9 @@ int getTrailingZerosCount(const MemRegion *R, CheckerContext &C) { return -1; ASTContext &ASTCtx = C.getASTContext(); - const QualType &PT = TR->getDesugaredValueType(ASTCtx); + const QualType PT = TR->getDesugaredValueType(ASTCtx); + if (PT->isIncompleteType()) + return -1; unsigned A = ASTCtx.getTypeAlignInChars(PT).getQuantity(); return llvm::APSInt::getUnsigned(A).countTrailingZeros(); } @@ -143,15 +160,21 @@ void CapabilityAlignmentChecker::checkPreStmt(const CastExpr *CE, const QualType &DstType = CE->getType(); if (!Src->getType()->isPointerType() || !DstType->isPointerType()) return; + const QualType &DstPointeeTy = DstType->getPointeeType(); + if (DstPointeeTy->isIncompleteType()) + return; - ASTContext &ASTCtx = C.getASTContext(); - int SrcTZC = getTrailingZerosCount(CE->getSubExpr(), C); + const SVal &SrcVal = C.getSVal(CE->getSubExpr()); + if (SrcVal.isConstant()) + return; // special value + int SrcTZC = getTrailingZerosCount(SrcVal, C); if (SrcTZC < 0) return; if ((unsigned)SrcTZC >= sizeof(unsigned int)*8) return; // Too aligned, probably a Zero unsigned SrcAlign = (1U << SrcTZC); - const QualType &DstPointeeTy = DstType->getPointeeType(); + + ASTContext &ASTCtx = C.getASTContext(); unsigned DstReqAlign = ASTCtx.getTypeAlignInChars(DstPointeeTy).getQuantity(); if (SrcAlign < DstReqAlign) { if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { @@ -183,7 +206,7 @@ void CapabilityAlignmentChecker::checkPostStmt(const CastExpr *CE, CheckerContext &C) const { CastKind CK = CE->getCastKind(); if (CK != CastKind::CK_BitCast && CK != CK_PointerToIntegral && - CK != CK_IntegralToPointer && CK != CK_LValueToRValue) + CK != CK_IntegralToPointer) return; int DstTZC = getTrailingZerosCount(CE, C); diff --git a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c index bd375aead4e1..0fa77330b9f7 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c @@ -8,17 +8,21 @@ double a[2048], *next = a; #define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) #define NULL (void*)0 +#define MINUS_ONE (void*)-1 uintptr_t *u; -void foo(void *v) { +void foo(void *v, int *pi, void *pv) { char *p0 = (char*)a; *(void**)p0 = v; // expected-warning{{Cast increases required alignment: 8 -> 16}} char *p1 = (char*)roundup2((uintptr_t)p0, sizeof(void*)); - *(void**)p1 = v; // no warn + *(void**)p1 = v; // no warning char *p2 = p1 + 5*sizeof(double); *(void**)p2 = v; // expected-warning{{Cast increases required alignment: 8 -> 16}} - if (u == NULL) - return; // no warning -} + *(void**)pi = v; // expected-warning{{Cast increases required alignment: 4 -> 16}} + *(void**)pv = v; // no warning + *(void**)next = v; // expected-warning{{Cast increases required alignment: 8 -> 16}} + if (u == NULL || u == MINUS_ONE) // no warning + return; +} From 8e8d16d2aae1bc5f2603699cc1228fbd3c92d53d Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 8 Aug 2023 18:19:29 +0100 Subject: [PATCH 19/77] [CHERI_CSA] CapabilityAlignmentChecker: support align check --- .../CHERI/CapabilityAlignmentChecker.cpp | 45 ++++++++++++++----- .../Checkers/CHERI/capability-alignment.c | 12 +++++ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp index 346f811c4c68..f8e2e33f75df 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp @@ -236,6 +236,18 @@ void CapabilityAlignmentChecker::checkPostStmt(const CastExpr *CE, } } +bool valueIsLTPow2(const Expr *E, unsigned P, CheckerContext &C) { + if (P >= sizeof(uint64_t) * 8) + return true; + SValBuilder &SVB = C.getSValBuilder(); + const NonLoc &B = SVB.makeIntVal((uint64_t)1 << P, true); + + ProgramStateRef State = C.getState(); + const SVal &V = C.getSVal(E); + auto LT = SVB.evalBinOp(State, BO_LT, V, B, E->getType()); + return !State->assume(LT.castAs(), false); +} + void CapabilityAlignmentChecker::checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const { int LeftTZ = getTrailingZerosCount(BO->getLHS(), C); @@ -247,18 +259,9 @@ void CapabilityAlignmentChecker::checkPostStmt(const BinaryOperator *BO, ProgramStateRef State = C.getState(); SVal ResVal = C.getSVal(BO); - if (ResVal.isUnknown()) { - const LocationContext *LCtx = C.getLocationContext(); - ResVal = C.getSValBuilder().conjureSymbolVal( - nullptr, BO, LCtx, BO->getType(), C.blockCount()); - State = State->BindExpr(BO, LCtx, ResVal); - } - - SymbolRef ResSymb = ResVal.getAsSymbol(); - if (!ResSymb) + if (!ResVal.isUnknown() && !ResVal.getAsSymbol()) return; - const SVal &RHSVal = C.getSVal(BO->getRHS()); int BitWidth = C.getASTContext().getTypeSize(BO->getType()); int Res = 0; @@ -267,7 +270,19 @@ void CapabilityAlignmentChecker::checkPostStmt(const BinaryOperator *BO, switch (OpCode) { case clang::BO_And: case clang::BO_AndAssign: - Res = std::max(LeftTZ, RightTZ); + if (valueIsLTPow2(BO->getLHS(), RightTZ, C) || + valueIsLTPow2(BO->getRHS(), LeftTZ, C)) { + /* Align check: p & (ALIGN - 1)*/ + if (ResVal.isUnknown()) { + ResVal = C.getSValBuilder().makeIntVal(0, true); + State = State->BindExpr(BO, C.getLocationContext(), ResVal); + C.addTransition(State); + return; + } else { + Res = BitWidth; + } + } else + Res = std::max(LeftTZ, RightTZ); break; case clang::BO_Or: case clang::BO_OrAssign: @@ -315,7 +330,13 @@ void CapabilityAlignmentChecker::checkPostStmt(const BinaryOperator *BO, if (Res > BitWidth) Res = BitWidth; - State = State->set(ResSymb, Res); + if (ResVal.isUnknown()) { + const LocationContext *LCtx = C.getLocationContext(); + ResVal = C.getSValBuilder().conjureSymbolVal( + nullptr, BO, LCtx, BO->getType(), C.blockCount()); + State = State->BindExpr(BO, LCtx, ResVal); + } + State = State->set(ResVal.getAsSymbol(), Res); const NoteTag *Tag = C.getNoteTag([LeftTZ, RightTZ, Res, OpCode] (PathSensitiveBugReport &BR) -> std::string { diff --git a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c index 0fa77330b9f7..5dbbd11cd92b 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c @@ -2,6 +2,7 @@ // RUN: -analyzer-checker=core,alpha.cheri.CapabilityAlignmentChecker typedef __uintcap_t uintptr_t; +typedef __typeof__(sizeof(int)) size_t; double a[2048], *next = a; @@ -26,3 +27,14 @@ void foo(void *v, int *pi, void *pv) { if (u == NULL || u == MINUS_ONE) // no warning return; } + +#define align_offset(A) \ + ((((uintptr_t)(A)&7) == 0) \ + ? 0 \ + : ((8 - ((uintptr_t)(A)&7)) & 7)) + +uintptr_t* bar(uintptr_t *p) { + uintptr_t offset = align_offset((char*)(p) + 2 *sizeof(size_t)); + p = (uintptr_t*)((char*)p + offset); // no warning + return p; +} From f1c7332c326cfecb7f059b2a48d01ce345a8f0fd Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 9 Aug 2023 12:40:08 +0100 Subject: [PATCH 20/77] [CHERI_CSA] CapabilityAlignmentChecker: array element alignment Rely on ElementRegion type alignment solely when shift value is unknown Except for char shifts --- .../Checkers/CHERI/CapabilityAlignmentChecker.cpp | 15 +++++++++++---- .../Checkers/CHERI/capability-alignment.c | 11 +++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp index f8e2e33f75df..0ec649592d4d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp @@ -92,16 +92,24 @@ int getTrailingZerosCount(SymbolRef Sym, CheckerContext &C) { int getTrailingZerosCount(const MemRegion *R, CheckerContext &C) { R = R->StripCasts(); + ASTContext &ASTCtx = C.getASTContext(); if (const ElementRegion *ER = R->getAs()) { - const MemRegion *Base = ER->getBaseRegion(); + const MemRegion *Base = ER->getSuperRegion(); int BaseTZC = -1; if (const SymbolicRegion *SymbBase = Base->getSymbolicBase()) BaseTZC = getTrailingZerosCount(SymbBase->getSymbol(), C); else BaseTZC = getTrailingZerosCount(Base, C); int IdxTZC = getTrailingZerosCount(ER->getIndex(), C); - if (BaseTZC >=0 && IdxTZC >= 0) - return std::min(BaseTZC, IdxTZC); + if (BaseTZC >=0) { + const QualType ElemTy = ER->getElementType(); + unsigned ElAlign = ASTCtx.getTypeAlignInChars(ElemTy).getQuantity(); + if (IdxTZC < 0 && ElAlign == 1) + return -1; + int ElemTyTZC = llvm::APSInt::getUnsigned(ElAlign).countTrailingZeros(); + return std::min(BaseTZC, std::max(IdxTZC, 0) + ElemTyTZC); + } + return -1; } if (R->getSymbolicBase()) @@ -111,7 +119,6 @@ int getTrailingZerosCount(const MemRegion *R, CheckerContext &C) { if (!TR) return -1; - ASTContext &ASTCtx = C.getASTContext(); const QualType PT = TR->getDesugaredValueType(ASTCtx); if (PT->isIncompleteType()) return -1; diff --git a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c index 5dbbd11cd92b..4c1f4c57ad8d 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c @@ -2,6 +2,7 @@ // RUN: -analyzer-checker=core,alpha.cheri.CapabilityAlignmentChecker typedef __uintcap_t uintptr_t; +typedef __intcap_t intptr_t; typedef __typeof__(sizeof(int)) size_t; double a[2048], *next = a; @@ -38,3 +39,13 @@ uintptr_t* bar(uintptr_t *p) { p = (uintptr_t*)((char*)p + offset); // no warning return p; } + +struct S { + intptr_t u[10]; + int i[10]; +}; +int baz(struct S *s) { + uintptr_t* p1 = (uintptr_t*)&s->u[3]; // no warning + uintptr_t* p2 = (uintptr_t*)&s->i[6]; // expected-warning{{Cast increases required alignment: 8 -> 16}} + return p2 - p1; +} From 880a3750ed8c237b7b22256bc65340335186cc93 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 10 Aug 2023 19:23:28 +0100 Subject: [PATCH 21/77] [CHERI_CSA] CapabilityAlignmentChecker: attribute __aligned__ --- .../CHERI/CapabilityAlignmentChecker.cpp | 13 +++++++++-- .../Checkers/CHERI/capability-alignment.c | 22 +++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp index 0ec649592d4d..36dbf8f88b7e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp @@ -118,11 +118,20 @@ int getTrailingZerosCount(const MemRegion *R, CheckerContext &C) { const TypedValueRegion *TR = R->getAs(); if (!TR) return -1; - const QualType PT = TR->getDesugaredValueType(ASTCtx); if (PT->isIncompleteType()) return -1; - unsigned A = ASTCtx.getTypeAlignInChars(PT).getQuantity(); + unsigned NaturalAlign = ASTCtx.getTypeAlignInChars(PT).getQuantity(); + + unsigned AlignAttrVal = 0; + if (auto DR = R->getAs()) { + if (auto AA = DR->getDecl()->getAttr()) { + if (!AA->isAlignmentDependent()) + AlignAttrVal = AA->getAlignment(ASTCtx) / ASTCtx.getCharWidth(); + } + } + + unsigned A = std::max(NaturalAlign, AlignAttrVal); return llvm::APSInt::getUnsigned(A).countTrailingZeros(); } diff --git a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c index 4c1f4c57ad8d..d66a40b34f4b 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c @@ -43,9 +43,27 @@ uintptr_t* bar(uintptr_t *p) { struct S { intptr_t u[10]; int i[10]; + int i_aligned[10] __attribute__((aligned(16))); }; -int baz(struct S *s) { +uintptr_t* struct_field(struct S *s) { uintptr_t* p1 = (uintptr_t*)&s->u[3]; // no warning uintptr_t* p2 = (uintptr_t*)&s->i[6]; // expected-warning{{Cast increases required alignment: 8 -> 16}} - return p2 - p1; + uintptr_t* p3 = (uintptr_t*)&s->i_aligned[6]; // FIXME: expected-warning{{Cast increases required alignment: 8 -> 16}} + return p3 + (p2 - p1); +} + +void local_var(void) { + char buf[4]; + char buf_underaligned[4] __attribute__((aligned(2))); + char buf_aligned[4] __attribute__((aligned(4))); + *(int*)buf = 42; // expected-warning{{Cast increases required alignment: 1 -> 4}} + *(int*)buf_underaligned = 42; // expected-warning{{Cast increases required alignment: 2 -> 4}} + *(int*)buf_aligned = 42; // no warning +} + +char st_buf[4]; +char st_buf_aligned[4] __attribute__((aligned(_Alignof(int*)))); +void static_var(void) { + *(int*)st_buf = 42; // expected-warning{{Cast increases required alignment: 1 -> 4}} + *(int*)st_buf_aligned = 42; // no warning } From d5ed9a3462638a0ba3c7c77377db55b334e75d92 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 14 Aug 2023 15:35:33 +0100 Subject: [PATCH 22/77] [CHERI_CSA] CapabilityAlignmentChecker: BugReporterVisitor --- .../CHERI/CapabilityAlignmentChecker.cpp | 126 +++++++++++++----- 1 file changed, 91 insertions(+), 35 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp index 36dbf8f88b7e..2ae30509e5c5 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp @@ -52,6 +52,24 @@ class CapabilityAlignmentChecker void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; void checkPostStmt(const CastExpr *BO, CheckerContext &C) const; void checkPreStmt(const CastExpr *BO, CheckerContext &C) const; + +private: + class AlignmentBugVisitor : public BugReporterVisitor { + public: + AlignmentBugVisitor(SymbolRef SR) : Sym(SR) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Sym); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; + private: + SymbolRef Sym; + }; }; } // namespace @@ -65,10 +83,12 @@ CapabilityAlignmentChecker::CapabilityAlignmentChecker() { namespace { -int getTrailingZerosCount(const SVal &V, CheckerContext &C); +int getTrailingZerosCount(const SVal &V, ProgramStateRef State, + ASTContext &ASTCtx); -int getTrailingZerosCount(SymbolRef Sym, CheckerContext &C) { - const int *Align = C.getState()->get(Sym); +int getTrailingZerosCount(SymbolRef Sym, ProgramStateRef State, + ASTContext &ASTCtx) { + const int *Align = State->get(Sym); if (Align) return *Align; @@ -77,7 +97,6 @@ int getTrailingZerosCount(SymbolRef Sym, CheckerContext &C) { if (BaseRegOrigin->getMemorySpace()->hasGlobalsOrParametersStorage()) { const QualType &SymTy = Sym->getType(); if (SymTy->isPointerType() && !isGenericPointerType(SymTy)) { - ASTContext &ASTCtx = C.getASTContext(); const QualType &PT = SymTy->getPointeeType(); if (!PT->isIncompleteType()) { unsigned A = ASTCtx.getTypeAlignInChars(PT).getQuantity(); @@ -89,18 +108,18 @@ int getTrailingZerosCount(SymbolRef Sym, CheckerContext &C) { return -1; } -int getTrailingZerosCount(const MemRegion *R, CheckerContext &C) { +int getTrailingZerosCount(const MemRegion *R, ProgramStateRef State, + ASTContext &ASTCtx) { R = R->StripCasts(); - ASTContext &ASTCtx = C.getASTContext(); if (const ElementRegion *ER = R->getAs()) { const MemRegion *Base = ER->getSuperRegion(); int BaseTZC = -1; if (const SymbolicRegion *SymbBase = Base->getSymbolicBase()) - BaseTZC = getTrailingZerosCount(SymbBase->getSymbol(), C); + BaseTZC = getTrailingZerosCount(SymbBase->getSymbol(), State, ASTCtx); else - BaseTZC = getTrailingZerosCount(Base, C); - int IdxTZC = getTrailingZerosCount(ER->getIndex(), C); + BaseTZC = getTrailingZerosCount(Base, State, ASTCtx); + int IdxTZC = getTrailingZerosCount(ER->getIndex(), State, ASTCtx); if (BaseTZC >=0) { const QualType ElemTy = ER->getElementType(); unsigned ElAlign = ASTCtx.getTypeAlignInChars(ElemTy).getQuantity(); @@ -135,7 +154,8 @@ int getTrailingZerosCount(const MemRegion *R, CheckerContext &C) { return llvm::APSInt::getUnsigned(A).countTrailingZeros(); } -int getTrailingZerosCount(const SVal &V, CheckerContext &C) { +int getTrailingZerosCount(const SVal &V, ProgramStateRef State, + ASTContext &ASTCtx) { if (V.isUnknownOrUndef()) return -1; @@ -148,13 +168,13 @@ int getTrailingZerosCount(const SVal &V, CheckerContext &C) { } if (SymbolRef Sym = V.getAsSymbol()) { - int Res = getTrailingZerosCount(Sym, C); + int Res = getTrailingZerosCount(Sym, State, ASTCtx); if (Res >=0) return Res; } if (const MemRegion *R = V.getAsRegion()) { - return getTrailingZerosCount(R, C); + return getTrailingZerosCount(R, State, ASTCtx); } return -1; @@ -162,7 +182,7 @@ int getTrailingZerosCount(const SVal &V, CheckerContext &C) { int getTrailingZerosCount(const Expr *E, CheckerContext &C) { const SVal &V = C.getSVal(E); - return getTrailingZerosCount(V, C); + return getTrailingZerosCount(V, C.getState(), C.getASTContext()); } } // namespace @@ -183,7 +203,7 @@ void CapabilityAlignmentChecker::checkPreStmt(const CastExpr *CE, const SVal &SrcVal = C.getSVal(CE->getSubExpr()); if (SrcVal.isConstant()) return; // special value - int SrcTZC = getTrailingZerosCount(SrcVal, C); + int SrcTZC = getTrailingZerosCount(SrcVal, C.getState(), C.getASTContext()); if (SrcTZC < 0) return; if ((unsigned)SrcTZC >= sizeof(unsigned int)*8) @@ -204,6 +224,9 @@ void CapabilityAlignmentChecker::checkPreStmt(const CastExpr *CE, auto W = std::make_unique( *CastAlignBug, ErrorMessage, ErrNode); W->addRange(CE->getSourceRange()); + if (SymbolRef S = SrcVal.getAsSymbol()) + W->addVisitor(std::make_unique(S)); + W->markInteresting(SrcVal); C.emitReport(std::move(W)); } } @@ -239,15 +262,7 @@ void CapabilityAlignmentChecker::checkPostStmt(const CastExpr *CE, } if (SymbolRef Sym = DstVal.getAsSymbol()) { State = State->set(Sym, SrcTZC); - const NoteTag *Tag = - C.getNoteTag([SrcTZC](PathSensitiveBugReport &BR) -> std::string { - SmallString<80> Msg; - llvm::raw_svector_ostream OS(Msg); - OS << "Alignment: "; - printAlign(OS, SrcTZC); - return std::string(OS.str()); - }); - C.addTransition(State, C.getPredecessor(), Tag); + C.addTransition(State); } } } @@ -353,20 +368,61 @@ void CapabilityAlignmentChecker::checkPostStmt(const BinaryOperator *BO, State = State->BindExpr(BO, LCtx, ResVal); } State = State->set(ResVal.getAsSymbol(), Res); - const NoteTag *Tag = - C.getNoteTag([LeftTZ, RightTZ, Res, OpCode] - (PathSensitiveBugReport &BR) -> std::string { - SmallString<80> Msg; - llvm::raw_svector_ostream OS(Msg); - OS << "Alignment: "; - printAlign(OS, LeftTZ); + C.addTransition(State); +} + +PathDiagnosticPieceRef +CapabilityAlignmentChecker::AlignmentBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { + + const Stmt *S = N->getStmtForDiagnostics(); + if (!S) + return nullptr; + const Expr *E = dyn_cast(S); + if (!E) + return nullptr; + + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + + ProgramStateRef State = N->getState(); + const int *NewAlign = State->get(N->getSVal(E).getAsSymbol()); + if (!NewAlign) + return nullptr; + + if (const CastExpr *CE = dyn_cast(E)) { + if (Sym != N->getSVal(S).getAsSymbol()) + return nullptr; + if (SymbolRef SrcSym = N->getSVal(CE->getSubExpr()).getAsSymbol()) + if (const int *OldAlign = State->get(SrcSym)) + if (*OldAlign == *NewAlign) + return nullptr; + + OS << "Alignment: "; + printAlign(OS, *NewAlign); + } else if (const BinaryOperator *BO = dyn_cast(E)) { + BinaryOperatorKind const OpCode = BO->getOpcode(); + OS << "Alignment: "; + if (!BO->isShiftOp() && !BO->isShiftAssignOp()) { + ASTContext &ASTCtx = N->getCodeDecl().getASTContext(); + int LTZ = getTrailingZerosCount(N->getSVal(BO->getLHS()), State, ASTCtx); + int RTZ = getTrailingZerosCount(N->getSVal(BO->getRHS()), State, ASTCtx); + if (LTZ >= 0 && RTZ >= 0) { + printAlign(OS, LTZ); OS << " " << BinaryOperator::getOpcodeStr(OpCode) << " "; - printAlign(OS, RightTZ); + printAlign(OS, RTZ); OS << " => "; - printAlign(OS, Res); - return std::string(OS.str()); - }); - C.addTransition(State, C.getPredecessor(), Tag); + } + } + printAlign(OS, *NewAlign); + } else + return nullptr; + + // Generate the extra diagnostic. + PathDiagnosticLocation const Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return std::make_shared(Pos, OS.str(), true); } void ento::registerCapabilityAlignmentChecker(CheckerManager &mgr) { From 31a123882df0a12cb994c543a938a4e17c89b0d2 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 15 Aug 2023 12:44:38 +0100 Subject: [PATCH 23/77] [CHERI_CSA] CapabilityAlignmentChecker: fix FP for comparison with void* --- .../CHERI/CapabilityAlignmentChecker.cpp | 22 +++++++++++++++---- .../Checkers/CHERI/capability-alignment.c | 9 ++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp index 2ae30509e5c5..01704ae863f5 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp @@ -31,9 +31,10 @@ #include "CHERIUtils.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include #include #include -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" using namespace clang; using namespace ento; @@ -185,16 +186,29 @@ int getTrailingZerosCount(const Expr *E, CheckerContext &C) { return getTrailingZerosCount(V, C.getState(), C.getASTContext()); } +/* Introduced by clang, not in C standard */ +bool isImplicitConversionFromVoidPtr(const Stmt *S, CheckerContext &C) { + using namespace clang::ast_matchers; + auto CmpM = binaryOperation(isComparisonOperator()); + auto VoidPtr = expr(hasType(pointerType(pointee(voidType())))); + auto CastM = implicitCastExpr(has(VoidPtr), hasType(pointerType()), + hasCastKind(CK_BitCast), hasParent(CmpM)); + auto M = match(expr(CastM), *S, C.getASTContext()); + return !M.empty(); +} + } // namespace void CapabilityAlignmentChecker::checkPreStmt(const CastExpr *CE, CheckerContext &C) const { - if (CE->getCastKind() != CastKind::CK_BitCast) + CastKind CK = CE->getCastKind(); + if (CK != CastKind::CK_BitCast && CK != CK_IntegralToPointer) + return; + if (isImplicitConversionFromVoidPtr(CE, C)) return; - const Expr *Src = CE->getSubExpr(); const QualType &DstType = CE->getType(); - if (!Src->getType()->isPointerType() || !DstType->isPointerType()) + if (!DstType->isPointerType()) return; const QualType &DstPointeeTy = DstType->getPointeeType(); if (DstPointeeTy->isIncompleteType()) diff --git a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c index d66a40b34f4b..0c3bf2f1fd2b 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c @@ -67,3 +67,12 @@ void static_var(void) { *(int*)st_buf = 42; // expected-warning{{Cast increases required alignment: 1 -> 4}} *(int*)st_buf_aligned = 42; // no warning } + +int voidptr_cast(int *ip1, int *ip2) { + intptr_t w = (intptr_t)(ip2) | 1; + int b1 = (ip1 == (int*)w); // expected-warning{{Cast increases required alignment: 1 -> 4}} + int b2 = (ip1 == (void*)w); // no-warn + return b1 || b2; +} + + From bdb8b4c6adde107d9cd5137da47edad4263b141c Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 17 Aug 2023 15:00:11 +0100 Subject: [PATCH 24/77] [CHERI_CSA] CapabilityAlignmentChecker: refactoring of MemRegion alignment --- .../CHERI/CapabilityAlignmentChecker.cpp | 65 +++++++++---------- .../Checkers/CHERI/capability-alignment.c | 15 +++-- 2 files changed, 38 insertions(+), 42 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp index 01704ae863f5..622e24be29db 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp @@ -113,46 +113,41 @@ int getTrailingZerosCount(const MemRegion *R, ProgramStateRef State, ASTContext &ASTCtx) { R = R->StripCasts(); - if (const ElementRegion *ER = R->getAs()) { - const MemRegion *Base = ER->getSuperRegion(); - int BaseTZC = -1; - if (const SymbolicRegion *SymbBase = Base->getSymbolicBase()) - BaseTZC = getTrailingZerosCount(SymbBase->getSymbol(), State, ASTCtx); - else - BaseTZC = getTrailingZerosCount(Base, State, ASTCtx); - int IdxTZC = getTrailingZerosCount(ER->getIndex(), State, ASTCtx); - if (BaseTZC >=0) { - const QualType ElemTy = ER->getElementType(); - unsigned ElAlign = ASTCtx.getTypeAlignInChars(ElemTy).getQuantity(); - if (IdxTZC < 0 && ElAlign == 1) + if (const SymbolicRegion *SR = R->getAs()) + return getTrailingZerosCount(SR->getSymbol(), State, ASTCtx); + + if (const TypedValueRegion *TR = R->getAs()) { + const QualType PT = TR->getDesugaredValueType(ASTCtx); + if (PT->isIncompleteType()) + return -1; + unsigned NaturalAlign = ASTCtx.getTypeAlignInChars(PT).getQuantity(); + + if (const ElementRegion *ER = R->getAs()) { + const MemRegion *Base = ER->getSuperRegion(); + int BaseTZC = getTrailingZerosCount(Base, State, ASTCtx); + int IdxTZC = getTrailingZerosCount(ER->getIndex(), State, ASTCtx); + if (BaseTZC >= 0) { + if (IdxTZC < 0 && NaturalAlign == 1) return -1; - int ElemTyTZC = llvm::APSInt::getUnsigned(ElAlign).countTrailingZeros(); - return std::min(BaseTZC, std::max(IdxTZC, 0) + ElemTyTZC); + int ElemTyTZC = + llvm::APSInt::getUnsigned(NaturalAlign).countTrailingZeros(); + return std::min(BaseTZC, std::max(IdxTZC, 0) + ElemTyTZC); + } + return -1; } - return -1; - } - if (R->getSymbolicBase()) - return -1; - - const TypedValueRegion *TR = R->getAs(); - if (!TR) - return -1; - const QualType PT = TR->getDesugaredValueType(ASTCtx); - if (PT->isIncompleteType()) - return -1; - unsigned NaturalAlign = ASTCtx.getTypeAlignInChars(PT).getQuantity(); - - unsigned AlignAttrVal = 0; - if (auto DR = R->getAs()) { - if (auto AA = DR->getDecl()->getAttr()) { - if (!AA->isAlignmentDependent()) - AlignAttrVal = AA->getAlignment(ASTCtx) / ASTCtx.getCharWidth(); + unsigned AlignAttrVal = 0; + if (auto DR = R->getAs()) { + if (auto AA = DR->getDecl()->getAttr()) { + if (!AA->isAlignmentDependent()) + AlignAttrVal = AA->getAlignment(ASTCtx) / ASTCtx.getCharWidth(); + } } - } - unsigned A = std::max(NaturalAlign, AlignAttrVal); - return llvm::APSInt::getUnsigned(A).countTrailingZeros(); + unsigned A = std::max(NaturalAlign, AlignAttrVal); + return llvm::APSInt::getUnsigned(A).countTrailingZeros(); + } + return -1; } int getTrailingZerosCount(const SVal &V, ProgramStateRef State, diff --git a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c index 0c3bf2f1fd2b..a606710ddec9 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c @@ -41,15 +41,16 @@ uintptr_t* bar(uintptr_t *p) { } struct S { - intptr_t u[10]; - int i[10]; - int i_aligned[10] __attribute__((aligned(16))); + intptr_t u[40]; + int i[40]; + int i_aligned[40] __attribute__((aligned(16))); }; -uintptr_t* struct_field(struct S *s) { +int struct_field(struct S *s) { uintptr_t* p1 = (uintptr_t*)&s->u[3]; // no warning - uintptr_t* p2 = (uintptr_t*)&s->i[6]; // expected-warning{{Cast increases required alignment: 8 -> 16}} - uintptr_t* p3 = (uintptr_t*)&s->i_aligned[6]; // FIXME: expected-warning{{Cast increases required alignment: 8 -> 16}} - return p3 + (p2 - p1); + uintptr_t* p2 = (uintptr_t*)&s->i[8]; // expected-warning{{Cast increases required alignment: 4 -> 16}} + uintptr_t* p3 = (uintptr_t*)&s->i_aligned[6]; // expected-warning{{Cast increases required alignment: 8 -> 16}} + uintptr_t* p4 = (uintptr_t*)&s->i_aligned[4]; // no warning + return (p4 - p3) + (p2 - p1); } void local_var(void) { From da5f4e6a0e117f82e8d87fc69ed3669f4ad646bc Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 17 Aug 2023 15:58:49 +0100 Subject: [PATCH 25/77] [CHERI_CSA] CapabilityAlignmentChecker: add allocation source location to warning --- .../Checkers/CHERI/CapabilityAlignmentChecker.cpp | 14 ++++++++++++++ .../Analysis/Checkers/CHERI/capability-alignment.c | 13 +++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp index 622e24be29db..1e9b99e3a0f7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp @@ -192,6 +192,14 @@ bool isImplicitConversionFromVoidPtr(const Stmt *S, CheckerContext &C) { return !M.empty(); } +const ValueDecl *getAllocationDecl(const MemRegion *MR) { + if (const DeclRegion *DR = MR->getAs()) + return DR->getDecl(); + if (const ElementRegion *ER = MR->getAs()) + return getAllocationDecl(ER->getSuperRegion()); + return nullptr; +} + } // namespace void CapabilityAlignmentChecker::checkPreStmt(const CastExpr *CE, @@ -236,6 +244,12 @@ void CapabilityAlignmentChecker::checkPreStmt(const CastExpr *CE, if (SymbolRef S = SrcVal.getAsSymbol()) W->addVisitor(std::make_unique(S)); W->markInteresting(SrcVal); + if (const MemRegion *MR = SrcVal.getAsRegion()) { + if (const ValueDecl *SrcDecl= getAllocationDecl(MR)) { + W->addNote("Original allocation", PathDiagnosticLocation::create( + SrcDecl, C.getSourceManager())); + } + } C.emitReport(std::move(W)); } } diff --git a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c index a606710ddec9..7b498a6a508d 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c @@ -5,7 +5,8 @@ typedef __uintcap_t uintptr_t; typedef __intcap_t intptr_t; typedef __typeof__(sizeof(int)) size_t; -double a[2048], *next = a; +double a[2048], // expected-note{{Original allocation}} + *next = a; #define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) @@ -42,8 +43,8 @@ uintptr_t* bar(uintptr_t *p) { struct S { intptr_t u[40]; - int i[40]; - int i_aligned[40] __attribute__((aligned(16))); + int i[40]; // expected-note{{Original allocation}} + int i_aligned[40] __attribute__((aligned(16))); // expected-note{{Original allocation}} }; int struct_field(struct S *s) { uintptr_t* p1 = (uintptr_t*)&s->u[3]; // no warning @@ -54,15 +55,15 @@ int struct_field(struct S *s) { } void local_var(void) { - char buf[4]; - char buf_underaligned[4] __attribute__((aligned(2))); + char buf[4]; // expected-note{{Original allocation}} + char buf_underaligned[4] __attribute__((aligned(2))); // expected-note{{Original allocation}} char buf_aligned[4] __attribute__((aligned(4))); *(int*)buf = 42; // expected-warning{{Cast increases required alignment: 1 -> 4}} *(int*)buf_underaligned = 42; // expected-warning{{Cast increases required alignment: 2 -> 4}} *(int*)buf_aligned = 42; // no warning } -char st_buf[4]; +char st_buf[4]; // expected-note{{Original allocation}} char st_buf_aligned[4] __attribute__((aligned(_Alignof(int*)))); void static_var(void) { *(int*)st_buf = 42; // expected-warning{{Cast increases required alignment: 1 -> 4}} From 75c9b7c9c4fc6129513f58642300931fc7d02096 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 18 Aug 2023 15:25:05 +0100 Subject: [PATCH 26/77] [CHERI_CSA] CapabilityAlignmentChecker: improve warning message --- .../CHERI/CapabilityAlignmentChecker.cpp | 168 ++++++++++++------ .../Checkers/CHERI/capability-alignment.c | 22 +-- 2 files changed, 123 insertions(+), 67 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp index 1e9b99e3a0f7..34fb494f27fe 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp @@ -29,12 +29,12 @@ //===----------------------------------------------------------------------===// #include "CHERIUtils.h" +#include #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include #include #include +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" using namespace clang; using namespace ento; @@ -45,6 +45,7 @@ class CapabilityAlignmentChecker : public Checker, check::PostStmt, check::PostStmt> { std::unique_ptr CastAlignBug; + std::unique_ptr CapCastAlignBug; public: @@ -55,6 +56,10 @@ class CapabilityAlignmentChecker void checkPreStmt(const CastExpr *BO, CheckerContext &C) const; private: + ExplodedNode *emitCastAlignWarn(CheckerContext &C, unsigned SrcAlign, + unsigned DstReqAlign, + const CastExpr *CE) const; + class AlignmentBugVisitor : public BugReporterVisitor { public: AlignmentBugVisitor(SymbolRef SR) : Sym(SR) {} @@ -78,8 +83,12 @@ class CapabilityAlignmentChecker REGISTER_MAP_WITH_PROGRAMSTATE(TrailingZerosMap, SymbolRef, int) CapabilityAlignmentChecker::CapabilityAlignmentChecker() { - CastAlignBug.reset( - new BugType(this, "Cast increases required alignment", "CHERI portability")); + CastAlignBug.reset(new BugType(this, + "Cast increases required alignment", + "Type Error")); + CapCastAlignBug.reset(new BugType(this, + "Cast increases required alignment to capability alignment", + "CHERI portability")); } namespace { @@ -192,14 +201,6 @@ bool isImplicitConversionFromVoidPtr(const Stmt *S, CheckerContext &C) { return !M.empty(); } -const ValueDecl *getAllocationDecl(const MemRegion *MR) { - if (const DeclRegion *DR = MR->getAs()) - return DR->getDecl(); - if (const ElementRegion *ER = MR->getAs()) - return getAllocationDecl(ER->getSuperRegion()); - return nullptr; -} - } // namespace void CapabilityAlignmentChecker::checkPreStmt(const CastExpr *CE, @@ -230,40 +231,10 @@ void CapabilityAlignmentChecker::checkPreStmt(const CastExpr *CE, ASTContext &ASTCtx = C.getASTContext(); unsigned DstReqAlign = ASTCtx.getTypeAlignInChars(DstPointeeTy).getQuantity(); if (SrcAlign < DstReqAlign) { - if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { - SmallString<350> ErrorMessage; - llvm::raw_svector_ostream OS(ErrorMessage); - OS << "Cast increases required alignment: "; - OS << SrcAlign; - OS << " -> "; - OS << DstReqAlign; - OS << ")"; - auto W = std::make_unique( - *CastAlignBug, ErrorMessage, ErrNode); - W->addRange(CE->getSourceRange()); - if (SymbolRef S = SrcVal.getAsSymbol()) - W->addVisitor(std::make_unique(S)); - W->markInteresting(SrcVal); - if (const MemRegion *MR = SrcVal.getAsRegion()) { - if (const ValueDecl *SrcDecl= getAllocationDecl(MR)) { - W->addNote("Original allocation", PathDiagnosticLocation::create( - SrcDecl, C.getSourceManager())); - } - } - C.emitReport(std::move(W)); - } + emitCastAlignWarn(C, SrcAlign, DstReqAlign, CE); } } -void printAlign(raw_ostream &OS, unsigned TZC) { - OS << "aligned("; - if (TZC < sizeof(unsigned long)*8) - OS << (1LU << TZC); - else - OS << "2^(" << TZC << ")"; - OS << ")"; -} - void CapabilityAlignmentChecker::checkPostStmt(const CastExpr *CE, CheckerContext &C) const { CastKind CK = CE->getCastKind(); @@ -393,6 +364,90 @@ void CapabilityAlignmentChecker::checkPostStmt(const BinaryOperator *BO, State = State->set(ResVal.getAsSymbol(), Res); C.addTransition(State); } +namespace { + +bool hasCapability(const QualType OrigTy, ASTContext &Ctx) { + QualType Ty = OrigTy.getCanonicalType(); + if (Ty->isCHERICapabilityType(Ctx, true)) + return true; + if (const auto *Record = dyn_cast(Ty)) { + for (const auto *Field : Record->getDecl()->fields()) { + if (hasCapability(Field->getType(), Ctx)) + return true; + } + return false; + } + if (const auto *Array = dyn_cast(Ty)) { + return hasCapability(Array->getElementType(), Ctx); + } + return false; +} + +void printAlign(raw_ostream &OS, unsigned TZC) { + OS << "aligned("; + if (TZC < sizeof(unsigned long)*8) + OS << (1LU << TZC); + else + OS << "2^(" << TZC << ")"; + OS << ")"; +} + +void describeOriginalAllocation(const MemRegion *MR, PathSensitiveBugReport &W, + const SourceManager &SM, + ASTContext &ASTCtx) { + if (const DeclRegion *DR = MR->getAs()) { + const ValueDecl *SrcDecl = DR->getDecl(); + SmallString<350> Note; + llvm::raw_svector_ostream OS2(Note); + const QualType &AllocType = SrcDecl->getType().getCanonicalType(); + OS2 << "Original allocation of type "; + OS2 << "'" << AllocType.getAsString() << "'"; + OS2 << " which has an alignment requirement "; + OS2 << ASTCtx.getTypeAlignInChars(AllocType).getQuantity(); + OS2 << " bytes"; + W.addNote(Note, PathDiagnosticLocation::create(SrcDecl, SM)); + } else if (const ElementRegion *ER = MR->getAs()) + describeOriginalAllocation(ER->getSuperRegion(), W, SM, ASTCtx); +} + +} // namespace + +ExplodedNode *CapabilityAlignmentChecker::emitCastAlignWarn( + CheckerContext &C, unsigned SrcAlign, unsigned DstReqAlign, + const CastExpr *CE) const { + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); + if (!ErrNode) + return nullptr; + + ASTContext &ASTCtx = C.getASTContext(); + const QualType &DstTy = CE->getType(); + bool DstAlignIsCap = hasCapability(DstTy->getPointeeType(), ASTCtx); + + SmallString<350> ErrorMessage; + llvm::raw_svector_ostream OS(ErrorMessage); + OS << "Pointer value aligned to a " << SrcAlign << " byte boundary"; + OS << " cast to type '" << DstTy.getAsString() << "'"; + OS << " with required"; + if (DstAlignIsCap) + OS << " capability"; + OS << " alignment " << DstReqAlign; + OS << " bytes"; + + auto W = std::make_unique( + DstAlignIsCap ? *CapCastAlignBug : *CastAlignBug, ErrorMessage, ErrNode); + W->addRange(CE->getSourceRange()); + + const SVal &SrcVal = C.getSVal(CE->getSubExpr()); + W->markInteresting(SrcVal); + if (SymbolRef S = SrcVal.getAsSymbol()) + W->addVisitor(std::make_unique(S)); + else if (const MemRegion *MR = SrcVal.getAsRegion()) { + describeOriginalAllocation(MR, *W, C.getSourceManager(), C.getASTContext()); + } + + C.emitReport(std::move(W)); + return ErrNode; +} PathDiagnosticPieceRef CapabilityAlignmentChecker::AlignmentBugVisitor::VisitNode( @@ -409,24 +464,26 @@ CapabilityAlignmentChecker::AlignmentBugVisitor::VisitNode( SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); + if (Sym != N->getSVal(S).getAsSymbol()) + return nullptr; + ProgramStateRef State = N->getState(); - const int *NewAlign = State->get(N->getSVal(E).getAsSymbol()); - if (!NewAlign) + ProgramStateRef Pred = N->getFirstPred()->getState(); + const int *NewAlign = State->get(Sym); + const int *OldAlign = Pred->get(Sym); + if (!NewAlign || (OldAlign && *NewAlign == *OldAlign)) return nullptr; if (const CastExpr *CE = dyn_cast(E)) { - if (Sym != N->getSVal(S).getAsSymbol()) - return nullptr; if (SymbolRef SrcSym = N->getSVal(CE->getSubExpr()).getAsSymbol()) - if (const int *OldAlign = State->get(SrcSym)) - if (*OldAlign == *NewAlign) + if (const int *SrcAlign = State->get(SrcSym)) + if (*SrcAlign == *NewAlign) return nullptr; + } - OS << "Alignment: "; - printAlign(OS, *NewAlign); - } else if (const BinaryOperator *BO = dyn_cast(E)) { + OS << "Alignment: "; + if (const BinaryOperator *BO = dyn_cast(E)) { BinaryOperatorKind const OpCode = BO->getOpcode(); - OS << "Alignment: "; if (!BO->isShiftOp() && !BO->isShiftAssignOp()) { ASTContext &ASTCtx = N->getCodeDecl().getASTContext(); int LTZ = getTrailingZerosCount(N->getSVal(BO->getLHS()), State, ASTCtx); @@ -438,9 +495,8 @@ CapabilityAlignmentChecker::AlignmentBugVisitor::VisitNode( OS << " => "; } } - printAlign(OS, *NewAlign); - } else - return nullptr; + } + printAlign(OS, *NewAlign); // Generate the extra diagnostic. PathDiagnosticLocation const Pos(S, BRC.getSourceManager(), diff --git a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c index 7b498a6a508d..3521bfb43eb4 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c @@ -5,7 +5,7 @@ typedef __uintcap_t uintptr_t; typedef __intcap_t intptr_t; typedef __typeof__(sizeof(int)) size_t; -double a[2048], // expected-note{{Original allocation}} +double a[2048], // expected-note{{Original allocation of type 'double[2048]'}} *next = a; #define roundup2(x, y) (((x)+((y)-1))&(~((y)-1))) @@ -16,15 +16,15 @@ uintptr_t *u; void foo(void *v, int *pi, void *pv) { char *p0 = (char*)a; - *(void**)p0 = v; // expected-warning{{Cast increases required alignment: 8 -> 16}} + *(void**)p0 = v; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'void * __capability * __capability' with required capability alignment 16 bytes}} char *p1 = (char*)roundup2((uintptr_t)p0, sizeof(void*)); *(void**)p1 = v; // no warning char *p2 = p1 + 5*sizeof(double); - *(void**)p2 = v; // expected-warning{{Cast increases required alignment: 8 -> 16}} + *(void**)p2 = v; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'void * __capability * __capability' with required capability alignment 16 bytes}} - *(void**)pi = v; // expected-warning{{Cast increases required alignment: 4 -> 16}} + *(void**)pi = v; // expected-warning{{Pointer value aligned to a 4 byte boundary cast to type 'void * __capability * __capability' with required capability alignment 16 bytes}} *(void**)pv = v; // no warning - *(void**)next = v; // expected-warning{{Cast increases required alignment: 8 -> 16}} + *(void**)next = v; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'void * __capability * __capability' with required capability alignment 16 bytes}} if (u == NULL || u == MINUS_ONE) // no warning return; @@ -48,8 +48,8 @@ struct S { }; int struct_field(struct S *s) { uintptr_t* p1 = (uintptr_t*)&s->u[3]; // no warning - uintptr_t* p2 = (uintptr_t*)&s->i[8]; // expected-warning{{Cast increases required alignment: 4 -> 16}} - uintptr_t* p3 = (uintptr_t*)&s->i_aligned[6]; // expected-warning{{Cast increases required alignment: 8 -> 16}} + uintptr_t* p2 = (uintptr_t*)&s->i[8]; // expected-warning{{Pointer value aligned to a 4 byte boundary cast to type 'uintptr_t * __capability' with required capability alignment 16 bytes}} + uintptr_t* p3 = (uintptr_t*)&s->i_aligned[6]; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'uintptr_t * __capability' with required capability alignment 16 bytes}} uintptr_t* p4 = (uintptr_t*)&s->i_aligned[4]; // no warning return (p4 - p3) + (p2 - p1); } @@ -58,21 +58,21 @@ void local_var(void) { char buf[4]; // expected-note{{Original allocation}} char buf_underaligned[4] __attribute__((aligned(2))); // expected-note{{Original allocation}} char buf_aligned[4] __attribute__((aligned(4))); - *(int*)buf = 42; // expected-warning{{Cast increases required alignment: 1 -> 4}} - *(int*)buf_underaligned = 42; // expected-warning{{Cast increases required alignment: 2 -> 4}} + *(int*)buf = 42; // expected-warning{{Pointer value aligned to a 1 byte boundary cast to type 'int * __capability' with required alignment 4 bytes}} + *(int*)buf_underaligned = 42; // expected-warning{{Pointer value aligned to a 2 byte boundary cast to type 'int * __capability' with required alignment 4 bytes}} *(int*)buf_aligned = 42; // no warning } char st_buf[4]; // expected-note{{Original allocation}} char st_buf_aligned[4] __attribute__((aligned(_Alignof(int*)))); void static_var(void) { - *(int*)st_buf = 42; // expected-warning{{Cast increases required alignment: 1 -> 4}} + *(int*)st_buf = 42; // expected-warning{{Pointer value aligned to a 1 byte boundary cast to type 'int * __capability' with required alignment 4 bytes}} *(int*)st_buf_aligned = 42; // no warning } int voidptr_cast(int *ip1, int *ip2) { intptr_t w = (intptr_t)(ip2) | 1; - int b1 = (ip1 == (int*)w); // expected-warning{{Cast increases required alignment: 1 -> 4}} + int b1 = (ip1 == (int*)w); // expected-warning{{Pointer value aligned to a 1 byte boundary cast to type 'int * __capability' with required alignment 4 bytes}} int b2 = (ip1 == (void*)w); // no-warn return b1 || b2; } From b97d24da4e74572bc5356aa7889f7ad4e2f25b33 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 22 Aug 2023 19:03:14 +0100 Subject: [PATCH 27/77] [CHERI_CSA] CapabilityAlignmentChecker: removing dead symbols --- .../CHERI/CapabilityAlignmentChecker.cpp | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp index 34fb494f27fe..e0e43120b46a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp @@ -43,7 +43,7 @@ using namespace cheri; namespace { class CapabilityAlignmentChecker : public Checker, check::PostStmt, - check::PostStmt> { + check::PostStmt, check::DeadSymbols> { std::unique_ptr CastAlignBug; std::unique_ptr CapCastAlignBug; @@ -54,6 +54,8 @@ class CapabilityAlignmentChecker void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; void checkPostStmt(const CastExpr *BO, CheckerContext &C) const; void checkPreStmt(const CastExpr *BO, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + private: ExplodedNode *emitCastAlignWarn(CheckerContext &C, unsigned SrcAlign, @@ -338,7 +340,7 @@ void CapabilityAlignmentChecker::checkPostStmt(const BinaryOperator *BO, case clang::BO_Shr: case clang::BO_ShrAssign: if (RHSVal.isConstant()) { - if (Optional NV = RHSVal.getAs()) + if (auto NV = RHSVal.getAs()) RHSConst = NV.getValue().getValue().getExtValue(); } if (BO->getOpcode() == BO_Shl || BO->getOpcode() == BO_ShlAssign) @@ -364,6 +366,23 @@ void CapabilityAlignmentChecker::checkPostStmt(const BinaryOperator *BO, State = State->set(ResVal.getAsSymbol(), Res); C.addTransition(State); } + +void CapabilityAlignmentChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + TrailingZerosMapTy TZMap = State->get(); + bool Updated = false; + for (TrailingZerosMapTy::iterator I = TZMap.begin(), + E = TZMap.end(); I != E; ++I) { + if (SymReaper.isDead(I->first)) { + State = State->remove(I->first); + Updated = true; + } + } + if (Updated) + C.addTransition(State); +} + namespace { bool hasCapability(const QualType OrigTy, ASTContext &Ctx) { From f54740e6855a5e9515e87e755cfbbbfad66bef3d Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 6 Sep 2023 12:35:46 +0100 Subject: [PATCH 28/77] [CHERI_CSA] move 3 checkers from CHERIAlpha to CHERI section ProvenanceSourceChecker, CapabilityCopyChecker, CapabilityAlignmentChecker --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 12 ++++++------ .../Analysis/Checkers/CHERI/capability-alignment.c | 2 +- .../Analysis/Checkers/CHERI/capability-copy-hybrid.c | 5 +++-- .../Checkers/CHERI/capability-copy-purecap.c | 3 ++- .../test/Analysis/Checkers/CHERI/provenance-source.c | 3 ++- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index dd07e613747f..664af098160d 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1744,15 +1744,11 @@ def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">, let ParentPackage = CHERI in { -} // end cheri - -let ParentPackage = CHERIAlpha in { - -def ProvenanceSourceChecker : Checker<"ProvenanceSourceChecker">, +def ProvenanceSourceChecker : Checker<"ProvenanceSource">, HelpText<"Check expressions with ambiguous provenance source.">, Documentation; -def CapabilityCopyChecker : Checker<"CapabilityCopyChecker">, +def CapabilityCopyChecker : Checker<"CapabilityCopy">, HelpText<"Check tag-stripping memory copy.">, Documentation; @@ -1760,4 +1756,8 @@ def CapabilityAlignmentChecker : Checker<"CapabilityAlignmentChecker">, HelpText<"Check underaligned pointers.">, Documentation; +} // end cheri + +let ParentPackage = CHERIAlpha in { + } // end alpha.cheri diff --git a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c index 3521bfb43eb4..7c7293a1854e 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-alignment.c @@ -1,5 +1,5 @@ // RUN: %cheri_purecap_cc1 -analyze -verify %s \ -// RUN: -analyzer-checker=core,alpha.cheri.CapabilityAlignmentChecker +// RUN: -analyzer-checker=core,cheri.CapabilityAlignmentChecker typedef __uintcap_t uintptr_t; typedef __intcap_t intptr_t; diff --git a/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c b/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c index 6ee4cb0135cc..89e975cc9f37 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c @@ -1,6 +1,7 @@ -// RUN: %cheri_cc1 -analyze -analyzer-checker=core,alpha.cheri.CapabilityCopyChecker -verify %s +// RUN: %cheri_cc1 -analyze -verify %s \ +// RUN: -analyzer-checker=core,cheri.CapabilityCopy -// Don't emit anywarnings fot hybrid mode +// Don't emit any warnings in hybrid mode // expected-no-diagnostics #define BLOCK_TYPE long diff --git a/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c b/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c index f7a23623cde1..bad1d06cf7f6 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c @@ -1,4 +1,5 @@ -// RUN: %cheri_purecap_cc1 -analyze -analyzer-checker=core,alpha.cheri.CapabilityCopyChecker -verify %s +// RUN: %cheri_purecap_cc1 -analyze -verify %s \ +// RUN: -analyzer-checker=core,cheri.CapabilityCopy typedef __intcap_t intptr_t; typedef __uintcap_t uintptr_t; diff --git a/clang/test/Analysis/Checkers/CHERI/provenance-source.c b/clang/test/Analysis/Checkers/CHERI/provenance-source.c index adaa1222e11c..deb73a967aeb 100644 --- a/clang/test/Analysis/Checkers/CHERI/provenance-source.c +++ b/clang/test/Analysis/Checkers/CHERI/provenance-source.c @@ -1,4 +1,5 @@ -// RUN: %cheri_purecap_cc1 -analyze -analyzer-checker=core,alpha.cheri.ProvenanceSourceChecker -verify %s +// RUN: %cheri_purecap_cc1 -analyze -verify %s \ +// RUN: -analyzer-checker=core,cheri.ProvenanceSource typedef __intcap_t intptr_t; typedef __uintcap_t uintptr_t; From 3b9ce71fe2e3d3be1db476b34e4992d322355d2e Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 7 Sep 2023 16:25:31 +0100 Subject: [PATCH 29/77] [CHERI_CSA] ProvenanceSourceChecker: propagate InvalidCap through UnaryOperator --- .../CHERI/ProvenanceSourceChecker.cpp | 51 +++++++++++++------ .../Checkers/CHERI/provenance-source.c | 8 +++ 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index 6027785e42bb..a0540a118038 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -26,6 +26,7 @@ namespace { class ProvenanceSourceChecker : public Checker, check::PreStmt, check::PostStmt, + check::PostStmt, check::DeadSymbols> { std::unique_ptr AmbiguousProvenanceBinOpBugType; std::unique_ptr AmbiguousProvenancePtrBugType; @@ -73,6 +74,7 @@ class ProvenanceSourceChecker : public Checker, void checkPostStmt(const CastExpr *CE, CheckerContext &C) const; void checkPreStmt(const CastExpr *CE, CheckerContext &C) const; void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; + void checkPostStmt(const UnaryOperator *BO, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; private: @@ -86,10 +88,11 @@ class ProvenanceSourceChecker : public Checker, ExplodedNode *emitPtrdiffAsIntCapWarn(const BinaryOperator *BO, CheckerContext &C) const; - static void propagateProvenanceInfoForBinOp(ExplodedNode *N, - const BinaryOperator *BO, + static void propagateProvenanceInfo(ExplodedNode *N, + const Expr *E, CheckerContext &C, - bool IsInvalidCap); + bool IsInvalidCap, + const NoteTag* Tag); }; } // namespace @@ -342,17 +345,17 @@ ExplodedNode *ProvenanceSourceChecker::emitAmbiguousProvenanceWarn( return ErrNode; } -void ProvenanceSourceChecker::propagateProvenanceInfoForBinOp( - ExplodedNode *N, const BinaryOperator *BO, CheckerContext &C, - bool IsInvalidCap) { +void ProvenanceSourceChecker::propagateProvenanceInfo( + ExplodedNode *N, const Expr *E, CheckerContext &C, + bool IsInvalidCap, const NoteTag* Tag) { ProgramStateRef State = N->getState(); - SVal ResVal = C.getSVal(BO); + SVal ResVal = C.getSVal(E); if (ResVal.isUnknown()) { const LocationContext *LCtx = C.getLocationContext(); ResVal = C.getSValBuilder().conjureSymbolVal( - nullptr, BO, LCtx, BO->getType(), C.blockCount()); - State = State->BindExpr(BO, LCtx, ResVal); + nullptr, E, LCtx, E->getType(), C.blockCount()); + State = State->BindExpr(E, LCtx, ResVal); } if (SymbolRef ResSym = ResVal.getAsSymbol()) @@ -363,11 +366,7 @@ void ProvenanceSourceChecker::propagateProvenanceInfoForBinOp( else return; // no result to propagate to - const NoteTag *BinOpTag = - IsInvalidCap ? nullptr // note will be added in BugVisitor - : C.getNoteTag("Binary operator has ambiguous provenance"); - - C.addTransition(State, N, BinOpTag); + C.addTransition(State, N, Tag); } // Report intcap binary expressions with ambiguous provenance, @@ -427,7 +426,29 @@ void ProvenanceSourceChecker::checkPostStmt(const BinaryOperator *BO, return; // Propagate info for result - propagateProvenanceInfoForBinOp(N, BO, C, InvalidCap); + const NoteTag *BinOpTag = + InvalidCap ? nullptr // note will be added in BugVisitor + : C.getNoteTag("Binary operator has ambiguous provenance"); + propagateProvenanceInfo(N, BO, C, InvalidCap, BinOpTag); +} + +void ProvenanceSourceChecker::checkPostStmt(const UnaryOperator *UO, + CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + + if (!UO->isArithmeticOp() && !UO->isIncrementDecrementOp()) + return; + + Expr *Op = UO->getSubExpr(); + if (!Op->getType()->isIntCapType()) + return; + + ProgramStateRef State = C.getState(); + const SVal &Val = C.getSVal(UO); + const SVal &SrcVal = C.getSVal(Op); + if (hasNoProvenance(State, SrcVal) && !hasNoProvenance(State, Val)) + propagateProvenanceInfo(C.getPredecessor(), UO, C, true, nullptr); } void ProvenanceSourceChecker::checkDeadSymbols(SymbolReaper &SymReaper, diff --git a/clang/test/Analysis/Checkers/CHERI/provenance-source.c b/clang/test/Analysis/Checkers/CHERI/provenance-source.c index deb73a967aeb..9192a9e2e762 100644 --- a/clang/test/Analysis/Checkers/CHERI/provenance-source.c +++ b/clang/test/Analysis/Checkers/CHERI/provenance-source.c @@ -4,6 +4,7 @@ typedef __intcap_t intptr_t; typedef __uintcap_t uintptr_t; typedef long int ptrdiff_t; +typedef __typeof__(sizeof(int)) size_t; char left_prov(int d, char *p) { intptr_t a = d; @@ -99,6 +100,13 @@ uintptr_t fn1(char *str, int f) { // expected-warning@-2{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} } +uintptr_t align_down(void *p, size_t alignment) { + uintptr_t sz = (uintptr_t)p; + uintptr_t mask = alignment - 1; + return (sz & ~mask); // expected-warning{{Result of '&' on capability type 'unsigned __intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'size_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} + // expected-warning@-1{{binary expression on capability types 'uintptr_t' (aka 'unsigned __intcap') and 'uintptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} +} + char* ptr_diff(char *s1, char *s2) { intptr_t a = (intptr_t)s1; intptr_t b = (intptr_t)s2; From 7f94d78609356ceac0b79bad7591331d8b84d446 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 8 Sep 2023 13:54:51 +0100 Subject: [PATCH 30/77] [CHERI_CSA] Enable cheri.* checkers by default on purecap --- clang/lib/Driver/ToolChains/Clang.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index a8307a077237..852f27070671 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -3237,6 +3237,14 @@ static void RenderAnalyzerOptions(const ArgList &Args, ArgStringList &CmdArgs, CmdArgs.push_back("-analyzer-checker=security.insecureAPI.vfork"); } + if (Triple.getEnvironment() == llvm::Triple::CheriPurecap || + // FIXME: checks below should eventually become unreachable when + // Triple is updated to purecap in ToolChain constructor + (Triple.isMIPS() && tools::mips::hasMipsAbiArg(Args, "purecap")) || + (Triple.isRISCV() && tools::riscv::isCheriPurecap(Args, Triple))) { + CmdArgs.push_back("-analyzer-checker=cheri"); + } + // Default nullability checks. CmdArgs.push_back("-analyzer-checker=nullability.NullPassedToNonnull"); CmdArgs.push_back("-analyzer-checker=nullability.NullReturnedFromNonnull"); From 218127ce290b88be269a2f2a3271bbe4b8d68326 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 8 Sep 2023 18:06:24 +0100 Subject: [PATCH 31/77] [CHERI_CSA] ProvenanceSourceChecker: add FixIts --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 7 ++++ .../CHERI/ProvenanceSourceChecker.cpp | 32 ++++++++++++++++++- .../Checkers/CHERI/provenance-source.c | 26 +++++++++++++++ clang/test/Analysis/analyzer-config.c | 1 + 4 files changed, 65 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 664af098160d..a65bd49dc18d 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1746,6 +1746,13 @@ let ParentPackage = CHERI in { def ProvenanceSourceChecker : Checker<"ProvenanceSource">, HelpText<"Check expressions with ambiguous provenance source.">, + CheckerOptions<[ + CmdLineOption + ]>, Documentation; def CapabilityCopyChecker : Checker<"CapabilityCopy">, diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index a0540a118038..af6cc7d684a7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -77,6 +77,8 @@ class ProvenanceSourceChecker : public Checker, void checkPostStmt(const UnaryOperator *BO, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + bool ShowFixIts = false; + private: // Utility ExplodedNode *emitAmbiguousProvenanceWarn(const BinaryOperator *BO, @@ -284,6 +286,23 @@ ExplodedNode *ProvenanceSourceChecker::emitPtrdiffAsIntCapWarn( return ErrNode; } +namespace { + +FixItHint addFixIt(const Expr *NDOp, CheckerContext &C, bool IsUnsigned) { + const SourceRange &SrcRange = NDOp->getSourceRange(); + bool InValidStr = true; + const StringRef &OpStr = + Lexer::getSourceText(CharSourceRange::getTokenRange(SrcRange), + C.getSourceManager(), C.getLangOpts(), &InValidStr); + if (!InValidStr) { + return FixItHint::CreateReplacement(SrcRange, + (IsUnsigned ? "(size_t)(" : "(ptrdiff_t)(") + OpStr.str() + ")"); + } + return {}; +} + +} // namespace + ExplodedNode *ProvenanceSourceChecker::emitAmbiguousProvenanceWarn( const BinaryOperator *BO, CheckerContext &C, bool LHSIsAddr, bool LHSIsNullDerived, @@ -327,6 +346,15 @@ ExplodedNode *ProvenanceSourceChecker::emitAmbiguousProvenanceWarn( *AmbiguousProvenanceBinOpBugType, ErrorMessage, ErrNode); R->addRange(BO->getSourceRange()); + if (ShowFixIts) { + if ((LHSIsAddr && RHSIsNullDerived) || (LHSIsNullDerived && RHSIsAddr)) { + const Expr *NDOp = LHSIsNullDerived ? BO->getLHS() : BO->getRHS(); + const FixItHint &Fixit = addFixIt(NDOp, C, IsUnsigned); + if (!Fixit.isNull()) + R->addFixItHint(Fixit); + } + } + const SVal &LHSVal = C.getSVal(BO->getLHS()); R->markInteresting(LHSVal); if (SymbolRef LS = LHSVal.getAsSymbol()) @@ -570,7 +598,9 @@ ProvenanceSourceChecker::Ptr2IntBugVisitor::VisitNode( } void ento::registerProvenanceSourceChecker(CheckerManager &mgr) { - mgr.registerChecker(); + auto *Checker = mgr.registerChecker(); + Checker->ShowFixIts = mgr.getAnalyzerOptions().getCheckerBooleanOption( + Checker, "ShowFixIts"); } bool ento::shouldRegisterProvenanceSourceChecker(const CheckerManager &Mgr) { diff --git a/clang/test/Analysis/Checkers/CHERI/provenance-source.c b/clang/test/Analysis/Checkers/CHERI/provenance-source.c index 9192a9e2e762..b2f124e89754 100644 --- a/clang/test/Analysis/Checkers/CHERI/provenance-source.c +++ b/clang/test/Analysis/Checkers/CHERI/provenance-source.c @@ -1,5 +1,10 @@ // RUN: %cheri_purecap_cc1 -analyze -verify %s \ // RUN: -analyzer-checker=core,cheri.ProvenanceSource +// RUN: %check_analyzer_fixit %s %t \ +// RUN: -triple mips64-unknown-freebsd -target-abi purecap -target-cpu cheri128 -cheri-size 128 \ +// RUN: -analyzer-checker=core,cheri.ProvenanceSource \ +// RUN: -analyzer-config cheri.ProvenanceSource:ShowFixIts=true \ +// RUN: -verify=non-nested,nested typedef __intcap_t intptr_t; typedef __uintcap_t uintptr_t; @@ -12,6 +17,9 @@ char left_prov(int d, char *p) { intptr_t s = b + a; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + + // CHECK-FIXES: intptr_t s = b + (ptrdiff_t)(a); + return *(char*)s;// expected-warning{{Capability with ambiguous provenance is used as pointer}} } @@ -21,6 +29,9 @@ char right_prov(unsigned d, char *p) { uintptr_t s = a + b; // expected-warning{{Result of '+' on capability type 'unsigned __intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'size_t'. Note: along this path, LHS was derived from NULL, RHS was derived from pointer}} // expected-warning@-1{{binary expression on capability types 'uintptr_t' (aka 'unsigned __intcap') and 'uintptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + + // CHECK-FIXES: intptr_t s = (ptrdiff_t)(a) + b; + return *(char*)s;// expected-warning{{Capability with ambiguous provenance is used as pointer}} } @@ -55,6 +66,7 @@ char right_prov_cond(int d, char *p, int x) { intptr_t s = a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS and RHS were derived from NULL}} // expected-warning@-1{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from NULL, RHS was derived from pointer}} // expected-warning@-2{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + return *(char*)s;// expected-warning{{Capability with ambiguous provenance is used as pointer}} } @@ -75,6 +87,9 @@ char add_const(int *p, int x) { intptr_t s = a | b; // expected-warning{{Result of '|' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + + // CHECK-FIXES: intptr_t s = a | (ptrdiff_t)(b); + return *(char*)s; // expected-warning{{Capability with ambiguous provenance is used as pointer}} } @@ -84,6 +99,9 @@ char add_var(int *p, int x, int c) { intptr_t s = a | b; // expected-warning{{Result of '|' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + + // CHECK-FIXES: intptr_t s = a | (ptrdiff_t)(b); + return *(char*)s; // expected-warning{{Capability with ambiguous provenance is used as pointer}} } @@ -98,6 +116,8 @@ uintptr_t fn1(char *str, int f) { return ((intptr_t)str & x); // expected-warning@-1{{Result of '&' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} // expected-warning@-2{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + + // CHECK-FIXES: return ((intptr_t)str & (ptrdiff_t)(x)); } uintptr_t align_down(void *p, size_t alignment) { @@ -105,6 +125,8 @@ uintptr_t align_down(void *p, size_t alignment) { uintptr_t mask = alignment - 1; return (sz & ~mask); // expected-warning{{Result of '&' on capability type 'unsigned __intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'size_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} // expected-warning@-1{{binary expression on capability types 'uintptr_t' (aka 'unsigned __intcap') and 'uintptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + + // CHECK-FIXES: return (sz & (size_t)(~mask)); } char* ptr_diff(char *s1, char *s2) { @@ -182,11 +204,15 @@ intptr_t foo(int d) { return b + a; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from pointer, RHS was derived from NULL}} // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + + // CHECK-FIXES: return b + (ptrdiff_t)(a); } intptr_t add(intptr_t a, intptr_t b) { return a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from NULL, RHS was derived from pointer}} // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} + + // CHECK-FIXES: return (ptrdiff_t)(a) + b; } intptr_t bar(int d) { diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index e06a8ae5604f..e490621c0733 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -33,6 +33,7 @@ // CHECK-NEXT: cfg-rich-constructors = true // CHECK-NEXT: cfg-scopes = false // CHECK-NEXT: cfg-temporary-dtors = true +// CHECK-NEXT: cheri.ProvenanceSource:ShowFixIts = false // CHECK-NEXT: consider-single-element-arrays-as-flexible-array-members = false // CHECK-NEXT: core.CallAndMessage:ArgInitializedness = true // CHECK-NEXT: core.CallAndMessage:ArgPointeeInitializedness = false From b84e4e5b0ae0c495d2193e775b1cd01bd11ea7ea Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 11 Sep 2023 18:14:12 +0100 Subject: [PATCH 32/77] [CHERI_CSA] Move cheri.CapabilityAlignmentChecker -> optin.portability.PointerAlignment --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 8 ++--- clang/lib/Driver/ToolChains/Clang.cpp | 1 + .../StaticAnalyzer/Checkers/CMakeLists.txt | 2 +- ...hecker.cpp => PointerAlignmentChecker.cpp} | 34 +++++++++---------- ...bility-alignment.c => pointer-alignment.c} | 2 +- 5 files changed, 24 insertions(+), 23 deletions(-) rename clang/lib/StaticAnalyzer/Checkers/{CHERI/CapabilityAlignmentChecker.cpp => PointerAlignmentChecker.cpp} (95%) rename clang/test/Analysis/{Checkers/CHERI/capability-alignment.c => pointer-alignment.c} (98%) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index a65bd49dc18d..b435a956685f 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1667,6 +1667,10 @@ def UnixAPIPortabilityChecker : Checker<"UnixAPI">, HelpText<"Finds implementation-defined behavior in UNIX/Posix functions">, Documentation; +def PointerAlignmentChecker : Checker<"PointerAlignment">, + HelpText<"Check underaligned pointers.">, + Documentation; + } // end optin.portability //===----------------------------------------------------------------------===// @@ -1759,10 +1763,6 @@ def CapabilityCopyChecker : Checker<"CapabilityCopy">, HelpText<"Check tag-stripping memory copy.">, Documentation; -def CapabilityAlignmentChecker : Checker<"CapabilityAlignmentChecker">, - HelpText<"Check underaligned pointers.">, - Documentation; - } // end cheri let ParentPackage = CHERIAlpha in { diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 852f27070671..d1139253b687 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -3243,6 +3243,7 @@ static void RenderAnalyzerOptions(const ArgList &Args, ArgStringList &CmdArgs, (Triple.isMIPS() && tools::mips::hasMipsAbiArg(Args, "purecap")) || (Triple.isRISCV() && tools::riscv::isCheriPurecap(Args, Triple))) { CmdArgs.push_back("-analyzer-checker=cheri"); + CmdArgs.push_back("-analyzer-checker=optin.portability.PointerAlignment"); } // Default nullability checks. diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index baa089ec96e7..b51e17e7ef48 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -27,7 +27,6 @@ add_clang_library(clangStaticAnalyzerCheckers CHERI/CHERIUtils.cpp CHERI/ProvenanceSourceChecker.cpp CHERI/CapabilityCopyChecker.cpp - CHERI/CapabilityAlignmentChecker.cpp ChrootChecker.cpp CloneChecker.cpp ContainerModeling.cpp @@ -93,6 +92,7 @@ add_clang_library(clangStaticAnalyzerCheckers ObjCUnusedIVarsChecker.cpp OSObjectCStyleCast.cpp PaddingChecker.cpp + PointerAlignmentChecker.cpp PointerArithChecker.cpp PointerIterationChecker.cpp PointerSortingChecker.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp similarity index 95% rename from clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp rename to clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index e0e43120b46a..bd0b2d3c45e0 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -1,4 +1,4 @@ -//=== CapabilityAlignmentChecker.cpp - Capability Alignment Checker -*- C++ ==// +//=== PointerAlignmentChecker.cpp - Capability Alignment Checker -*- C++ ==// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -28,20 +28,20 @@ // //===----------------------------------------------------------------------===// -#include "CHERIUtils.h" -#include +#include "CHERI/CHERIUtils.h" #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include #include #include -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" using namespace clang; using namespace ento; using namespace cheri; namespace { -class CapabilityAlignmentChecker +class PointerAlignmentChecker : public Checker, check::PostStmt, check::PostStmt, check::DeadSymbols> { std::unique_ptr CastAlignBug; @@ -49,7 +49,7 @@ class CapabilityAlignmentChecker public: - CapabilityAlignmentChecker(); + PointerAlignmentChecker(); void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; void checkPostStmt(const CastExpr *BO, CheckerContext &C) const; @@ -84,7 +84,7 @@ class CapabilityAlignmentChecker REGISTER_MAP_WITH_PROGRAMSTATE(TrailingZerosMap, SymbolRef, int) -CapabilityAlignmentChecker::CapabilityAlignmentChecker() { +PointerAlignmentChecker::PointerAlignmentChecker() { CastAlignBug.reset(new BugType(this, "Cast increases required alignment", "Type Error")); @@ -205,7 +205,7 @@ bool isImplicitConversionFromVoidPtr(const Stmt *S, CheckerContext &C) { } // namespace -void CapabilityAlignmentChecker::checkPreStmt(const CastExpr *CE, +void PointerAlignmentChecker::checkPreStmt(const CastExpr *CE, CheckerContext &C) const { CastKind CK = CE->getCastKind(); if (CK != CastKind::CK_BitCast && CK != CK_IntegralToPointer) @@ -237,7 +237,7 @@ void CapabilityAlignmentChecker::checkPreStmt(const CastExpr *CE, } } -void CapabilityAlignmentChecker::checkPostStmt(const CastExpr *CE, +void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, CheckerContext &C) const { CastKind CK = CE->getCastKind(); if (CK != CastKind::CK_BitCast && CK != CK_PointerToIntegral && @@ -275,7 +275,7 @@ bool valueIsLTPow2(const Expr *E, unsigned P, CheckerContext &C) { return !State->assume(LT.castAs(), false); } -void CapabilityAlignmentChecker::checkPostStmt(const BinaryOperator *BO, +void PointerAlignmentChecker::checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const { int LeftTZ = getTrailingZerosCount(BO->getLHS(), C); if (LeftTZ < 0) @@ -367,7 +367,7 @@ void CapabilityAlignmentChecker::checkPostStmt(const BinaryOperator *BO, C.addTransition(State); } -void CapabilityAlignmentChecker::checkDeadSymbols(SymbolReaper &SymReaper, +void PointerAlignmentChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { ProgramStateRef State = C.getState(); TrailingZerosMapTy TZMap = State->get(); @@ -431,7 +431,8 @@ void describeOriginalAllocation(const MemRegion *MR, PathSensitiveBugReport &W, } // namespace -ExplodedNode *CapabilityAlignmentChecker::emitCastAlignWarn( +ExplodedNode * +PointerAlignmentChecker::emitCastAlignWarn( CheckerContext &C, unsigned SrcAlign, unsigned DstReqAlign, const CastExpr *CE) const { ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); @@ -468,8 +469,7 @@ ExplodedNode *CapabilityAlignmentChecker::emitCastAlignWarn( return ErrNode; } -PathDiagnosticPieceRef -CapabilityAlignmentChecker::AlignmentBugVisitor::VisitNode( +PathDiagnosticPieceRef PointerAlignmentChecker::AlignmentBugVisitor::VisitNode( const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) { @@ -523,10 +523,10 @@ CapabilityAlignmentChecker::AlignmentBugVisitor::VisitNode( return std::make_shared(Pos, OS.str(), true); } -void ento::registerCapabilityAlignmentChecker(CheckerManager &mgr) { - mgr.registerChecker(); +void ento::registerPointerAlignmentChecker(CheckerManager &mgr) { + mgr.registerChecker(); } -bool ento::shouldRegisterCapabilityAlignmentChecker(const CheckerManager &Mgr) { +bool ento::shouldRegisterPointerAlignmentChecker(const CheckerManager &Mgr) { return true; } \ No newline at end of file diff --git a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c b/clang/test/Analysis/pointer-alignment.c similarity index 98% rename from clang/test/Analysis/Checkers/CHERI/capability-alignment.c rename to clang/test/Analysis/pointer-alignment.c index 7c7293a1854e..3c4e7aac637a 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-alignment.c +++ b/clang/test/Analysis/pointer-alignment.c @@ -1,5 +1,5 @@ // RUN: %cheri_purecap_cc1 -analyze -verify %s \ -// RUN: -analyzer-checker=core,cheri.CapabilityAlignmentChecker +// RUN: -analyzer-checker=core,optin.portability.PointerAlignment typedef __uintcap_t uintptr_t; typedef __intcap_t intptr_t; From a7f5025ad60307c89407b43d86f4437ab5e17c9e Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 14 Sep 2023 18:30:46 +0100 Subject: [PATCH 33/77] [CHERI_CSA] CapabilityCopyChecker: add ReportForCharPtr option --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 9 ++++ .../Checkers/CHERI/CHERIUtils.cpp | 6 +-- .../Checkers/CHERI/CHERIUtils.h | 2 +- .../Checkers/CHERI/CapabilityCopyChecker.cpp | 47 +++++++++++-------- clang/test/Analysis/analyzer-config.c | 1 + 5 files changed, 42 insertions(+), 23 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index b435a956685f..3986433db544 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1761,6 +1761,15 @@ def ProvenanceSourceChecker : Checker<"ProvenanceSource">, def CapabilityCopyChecker : Checker<"CapabilityCopy">, HelpText<"Check tag-stripping memory copy.">, + CheckerOptions<[ + CmdLineOption + ]>, Documentation; } // end cheri diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp index f338e988984d..bebb553ea0ea 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp @@ -30,9 +30,9 @@ CharUnits getCapabilityTypeAlign(ASTContext &ASTCtx) { return ASTCtx.getTypeAlignInChars(ASTCtx.VoidPtrTy); } -bool isGenericPointerType(const QualType T) { - return T->isVoidPointerType() || - (T->isPointerType() && T->getPointeeType()->isCharType()); +bool isGenericPointerType(const QualType T, bool AcceptCharPtr) { + return T->isVoidPointerType() || (AcceptCharPtr && T->isPointerType() && + T->getPointeeType()->isCharType()); } } // end of namespace: cheri diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h index b530805d81b2..8aeecb4881d9 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h @@ -23,7 +23,7 @@ CharUnits getCapabilityTypeSize(ASTContext &ASTCtx); CharUnits getCapabilityTypeAlign(ASTContext &ASTCtx); -bool isGenericPointerType(const QualType T); +bool isGenericPointerType(const QualType T, bool AcceptCharPtr = true); } // end of namespace: cheri } // end of namespace: ento diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index 4aeb6c3e9be6..352c84513b43 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -83,6 +83,8 @@ class CapabilityCopyChecker :public CheckergetSuperRegion(); } -bool isVoidOrCharPtrArgRegion(const MemRegion *Reg) { +bool isVoidOrCharPtrArgRegion(const MemRegion *Reg, bool AcceptCharPtr) { if (!Reg) return false; @@ -136,7 +138,7 @@ bool isVoidOrCharPtrArgRegion(const MemRegion *Reg) { return false; SymbolRef Sym = SymReg->getSymbol(); - if (!isGenericPointerType(Sym->getType())) + if (!isGenericPointerType(Sym->getType(), AcceptCharPtr)) return false; // 2. void* symbol is function argument @@ -145,7 +147,7 @@ bool isVoidOrCharPtrArgRegion(const MemRegion *Reg) { BaseRegOrigin->getMemorySpace()->hasStackParametersStorage(); } -CHERITagState getTagState(SVal Val, CheckerContext &C, +CHERITagState getTagState(SVal Val, CheckerContext &C, bool AcceptCharPtr, bool AcceptUnaligned = true) { if (Val.isUnknownOrUndef()) return CHERITagState::getUnknown(); @@ -162,14 +164,14 @@ CHERITagState getTagState(SVal Val, CheckerContext &C, const MemRegion *SuperReg = stripNonCapShift(MR, C.getASTContext()); if (isa(SuperReg)) return CHERITagState::getUnknown(); // not a part of capability - if (isVoidOrCharPtrArgRegion(SuperReg) && + if (isVoidOrCharPtrArgRegion(SuperReg, AcceptCharPtr) && (AcceptUnaligned || !S->contains(SuperReg)) && !S->contains(SuperReg)) return CHERITagState::getMayBeTagged(); if (MR != SuperReg && isa(SuperReg)) { const SVal &SuperVal = C.getState()->getSVal(SuperReg); if (Val != SuperVal) - return getTagState(SuperVal, C); + return getTagState(SuperVal, C, AcceptCharPtr); } } } @@ -178,6 +180,7 @@ CHERITagState getTagState(SVal Val, CheckerContext &C, } bool isCapabilityStorage(CheckerContext &C, const MemRegion *R, + bool AcceptCharPtr, bool AcceptUnaligned = true) { const MemRegion *BaseReg = stripNonCapShift(R, C.getASTContext()); if (!AcceptUnaligned && C.getState()->contains(BaseReg)) @@ -186,7 +189,7 @@ bool isCapabilityStorage(CheckerContext &C, const MemRegion *R, return false; if (const auto *SymR = dyn_cast(BaseReg)) { QualType const Ty = SymR->getSymbol()->getType(); - if (isGenericPointerType(Ty)) + if (isGenericPointerType(Ty, AcceptCharPtr)) return true; return isPointerToCapTy(Ty, C.getASTContext()); } @@ -211,7 +214,7 @@ void CapabilityCopyChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, ProgramStateRef State = C.getState(); QualType const Ty = l.getType(ASTCtx); QualType const ArgValTy = Ty->getPointeeType(); - if (!isGenericPointerType(ArgValTy)) + if (!isGenericPointerType(ArgValTy, ReportForCharPtr)) return; /* Loading VoidPtr function argument */ @@ -354,12 +357,12 @@ const MemRegion *isCapAlignCheck(const BinaryOperator *BO, CheckerContext &C) { } bool handleAlignmentCheck(const BinaryOperator *BO, ProgramStateRef State, - CheckerContext &C) { + CheckerContext &C, bool AcceptCharPtr) { auto ExprPtrReg = isCapAlignCheck(BO, C); if (!ExprPtrReg) return false; - if (!isVoidOrCharPtrArgRegion(ExprPtrReg)) + if (!isVoidOrCharPtrArgRegion(ExprPtrReg, AcceptCharPtr)) return false; SVal AndVal = C.getSVal(BO); @@ -405,11 +408,11 @@ void CapabilityCopyChecker::checkBind(SVal L, SVal V, const Stmt *S, return; /* Non-capability scalar store */ - const CHERITagState &ValTag = getTagState(V, C); + const CHERITagState &ValTag = getTagState(V, C, ReportForCharPtr); if (!ValTag.isTagged() && !ValTag.mayBeTagged()) return; - if (!isCapabilityStorage(C, MR)) + if (!isCapabilityStorage(C, MR, ReportForCharPtr)) return; if (ValTag.mayBeTagged() && !isPartOfCopyingSequence(PointeeTy, V, S, C)) @@ -432,7 +435,9 @@ namespace { using namespace clang::ast_matchers; using namespace clang::ast_matchers::internal; -ProgramStateRef markOriginAsCStringForMatch(const Stmt *E, Matcher D, CheckerContext &C) { +ProgramStateRef markOriginAsCStringForMatch(const Stmt *E, Matcher D, + CheckerContext &C, + bool AcceptCharPtr) { auto M = match(D, *E, C.getASTContext()); bool Updated = false; @@ -450,7 +455,7 @@ ProgramStateRef markOriginAsCStringForMatch(const Stmt *E, Matcher D, Chec const MemRegion *BaseReg = stripNonCapShift(R, C.getASTContext()); - if (isVoidOrCharPtrArgRegion(BaseReg)) { + if (isVoidOrCharPtrArgRegion(BaseReg, AcceptCharPtr)) { /* It's probably a string, so it's not to be regarded as a potential * pointer to capability */ State = State->add(BaseReg); @@ -510,7 +515,8 @@ void CapabilityCopyChecker::checkBranchCondition(const Stmt *Cond, auto CharMatcher = expr(hasType(isAnyCharacter())).bind("c"); auto CondMatcher = stmt(expr(ignoringParenCasts(CharMatcher))); - const ProgramStateRef N = markOriginAsCStringForMatch(Cond, CondMatcher, C); + const ProgramStateRef N = + markOriginAsCStringForMatch(Cond, CondMatcher, C, ReportForCharPtr); bool updated = checkForWhileBoundVar(Cond, C, N ? N : C.getState()); if (!updated && N) C.addTransition(N); @@ -525,7 +531,8 @@ void CapabilityCopyChecker::checkPostStmt(const ArraySubscriptExpr *E, auto CharMatcher = expr(hasType(isAnyCharacter())).bind("c"); auto IndexMatcher = stmt(expr(ignoringParenCasts(CharMatcher))); - const ProgramStateRef N = markOriginAsCStringForMatch(Index, IndexMatcher, C); + const ProgramStateRef N = + markOriginAsCStringForMatch(Index, IndexMatcher, C, ReportForCharPtr); if (N) C.addTransition(N); } @@ -598,11 +605,12 @@ void CapabilityCopyChecker::checkPostStmt(const BinaryOperator *BO, auto Arithm = binaryOperation(BitOps, has(CharMatcher)); auto BOM = anyOf(Cmp, Arithm); - const ProgramStateRef NewState = markOriginAsCStringForMatch(BO, BOM, C); + const ProgramStateRef NewState = + markOriginAsCStringForMatch(BO, BOM, C, ReportForCharPtr); if (NewState) State = NewState; - bool updated = handleAlignmentCheck(BO, State, C); + bool updated = handleAlignmentCheck(BO, State, C, ReportForCharPtr); if (!updated && NewState) C.addTransition(NewState); } @@ -630,8 +638,9 @@ void CapabilityCopyChecker::checkPreCall(const CallEvent &Call, } void ento::registerCapabilityCopyChecker(CheckerManager &mgr) { - mgr.registerChecker(); - + auto *Checker = mgr.registerChecker(); + Checker->ReportForCharPtr = mgr.getAnalyzerOptions().getCheckerBooleanOption( + Checker, "ReportForCharPtr"); } bool ento::shouldRegisterCapabilityCopyChecker(const CheckerManager &Mgr) { diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index e490621c0733..7a6bc7831a2e 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -33,6 +33,7 @@ // CHECK-NEXT: cfg-rich-constructors = true // CHECK-NEXT: cfg-scopes = false // CHECK-NEXT: cfg-temporary-dtors = true +// CHECK-NEXT: cheri.CapabilityCopy:ReportForCharPtr = true // CHECK-NEXT: cheri.ProvenanceSource:ShowFixIts = false // CHECK-NEXT: consider-single-element-arrays-as-flexible-array-members = false // CHECK-NEXT: core.CallAndMessage:ArgInitializedness = true From aa7f973e21eb8ef44e5e090332f8d85ab0e4a6c6 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 15 Sep 2023 17:59:52 +0100 Subject: [PATCH 34/77] [CHERI_CSA] Enable alpha.core.PointerSub by default for CHERI --- clang/lib/Driver/ToolChains/Clang.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index d1139253b687..9b20e4e4941d 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -3244,6 +3244,7 @@ static void RenderAnalyzerOptions(const ArgList &Args, ArgStringList &CmdArgs, (Triple.isRISCV() && tools::riscv::isCheriPurecap(Args, Triple))) { CmdArgs.push_back("-analyzer-checker=cheri"); CmdArgs.push_back("-analyzer-checker=optin.portability.PointerAlignment"); + CmdArgs.push_back("-analyzer-checker=alpha.core.PointerSub"); } // Default nullability checks. From 762ec72542f90c1330cd6ae84b33f4b42cfc7359 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 27 Sep 2023 18:22:33 +0100 Subject: [PATCH 35/77] [CHERI_CSA] Support non-constant offsets to ElementRegion --- .../Core/PathSensitive/ProgramState.h | 6 +- .../StaticAnalyzer/Core/PathSensitive/Store.h | 3 +- clang/lib/StaticAnalyzer/Core/Store.cpp | 59 ++++++++++--------- 3 files changed, 37 insertions(+), 31 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h index 9927b6340793..de228b0b29d3 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h @@ -794,9 +794,11 @@ inline SVal ProgramState::getLValue(const IndirectFieldDecl *D, return Base; } -inline SVal ProgramState::getLValue(QualType ElementType, SVal Idx, SVal Base) const{ +inline SVal ProgramState::getLValue(QualType ElementType, SVal Idx, + SVal Base) const { if (Optional N = Idx.getAs()) - return getStateManager().StoreMgr->getLValueElement(ElementType, *N, Base); + return getStateManager().StoreMgr->getLValueElement(this, ElementType, *N, + Base); return UnknownVal(); } diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h index 2ef083aa1646..4d4b24947cf5 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h @@ -146,7 +146,8 @@ class StoreManager { return getLValueFieldOrIvar(D, Base); } - virtual SVal getLValueElement(QualType elementType, NonLoc offset, SVal Base); + virtual SVal getLValueElement(ProgramStateRef State, QualType elementType, + NonLoc offset, SVal Base); /// ArrayToPointer - Used by ExprEngine::VistCast to handle implicit /// conversions between arrays and pointers. diff --git a/clang/lib/StaticAnalyzer/Core/Store.cpp b/clang/lib/StaticAnalyzer/Core/Store.cpp index 96e8878da616..257b92ebc9bb 100644 --- a/clang/lib/StaticAnalyzer/Core/Store.cpp +++ b/clang/lib/StaticAnalyzer/Core/Store.cpp @@ -440,9 +440,24 @@ SVal StoreManager::getLValueIvar(const ObjCIvarDecl *decl, SVal base) { return getLValueFieldOrIvar(decl, base); } -SVal StoreManager::getLValueElement(QualType elementType, NonLoc Offset, - SVal Base) { +static const SVal getNewIndex(ProgramStateRef State, SValBuilder &SVB, + NonLoc Offset, NonLoc BaseIdx) { + if (isa(BaseIdx) && isa(Offset)) { + const llvm::APSInt &BIdxI = BaseIdx.castAs().getValue(); + assert(BIdxI.isSigned()); + const llvm::APSInt &OffI = Offset.castAs().getValue(); + const llvm::APSInt &NewIdxI = BIdxI + OffI; + return nonloc::ConcreteInt(SVB.getBasicValueFactory().getValue(NewIdxI)); + } + if (isa(BaseIdx) || isa(Offset)) + return UnknownVal(); + + const QualType &Ty = SVB.getArrayIndexType(); + return SVB.evalBinOpNN(State, BO_Add, BaseIdx, Offset, Ty); +} +SVal StoreManager::getLValueElement(ProgramStateRef State, QualType elementType, + NonLoc Offset, SVal Base) { // Special case, if index is 0, return the same type as if // this was not an array dereference. if (Offset.isZeroConstant()) { @@ -486,36 +501,24 @@ SVal StoreManager::getLValueElement(QualType elementType, NonLoc Offset, BaseRegion, Ctx)); } - SVal BaseIdx = ElemR->getIndex(); + const SubRegion *ArrayR = cast(ElemR->getSuperRegion()); + // Avoid creating NewIndex if BaseIdx is 0 + if (!isa(BaseRegion->StripCasts())) + return loc::MemRegionVal( + MRMgr.getElementRegion(elementType, Offset, ArrayR, Ctx)); - if (!isa(BaseIdx)) + SVal BaseIdx = ElemR->getIndex(); + if (!isa(BaseIdx)) return UnknownVal(); - const llvm::APSInt &BaseIdxI = - BaseIdx.castAs().getValue(); - - // Only allow non-integer offsets if the base region has no offset itself. - // FIXME: This is a somewhat arbitrary restriction. We should be using - // SValBuilder here to add the two offsets without checking their types. - if (!isa(Offset)) { - if (isa(BaseRegion->StripCasts())) - return UnknownVal(); - - return loc::MemRegionVal(MRMgr.getElementRegion( - elementType, Offset, cast(ElemR->getSuperRegion()), Ctx)); + SVal NIdx = getNewIndex(State, svalBuilder, Offset, BaseIdx.castAs()); + if (Optional NewIdx = NIdx.getAs()) { + // Construct the new ElementRegion. + return loc::MemRegionVal( + MRMgr.getElementRegion(elementType, NewIdx.getValue(), ArrayR, Ctx)); } - - const llvm::APSInt& OffI = Offset.castAs().getValue(); - assert(BaseIdxI.isSigned()); - - // Compute the new index. - nonloc::ConcreteInt NewIdx(svalBuilder.getBasicValueFactory().getValue(BaseIdxI + - OffI)); - - // Construct the new ElementRegion. - const SubRegion *ArrayR = cast(ElemR->getSuperRegion()); - return loc::MemRegionVal(MRMgr.getElementRegion(elementType, NewIdx, ArrayR, - Ctx)); + + return UnknownVal(); } StoreManager::BindingsHandler::~BindingsHandler() = default; From 7bffe6b28f5160a8b2404519bba2933562bba012 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 27 Sep 2023 18:23:30 +0100 Subject: [PATCH 36/77] [CHERI_CSA] PointerAlignmentChecker: improve alignment tracking When a value with unknown alignment is converted to the type with non-trivial alignment requirements assume it to be aligned to that type. --- .../Checkers/PointerAlignmentChecker.cpp | 34 +++++++++++++------ clang/test/Analysis/pointer-alignment.c | 18 ++++++++++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index bd0b2d3c45e0..a554152d46ae 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -134,17 +134,18 @@ int getTrailingZerosCount(const MemRegion *R, ProgramStateRef State, unsigned NaturalAlign = ASTCtx.getTypeAlignInChars(PT).getQuantity(); if (const ElementRegion *ER = R->getAs()) { + int ElTyTZ = llvm::APSInt::getUnsigned(NaturalAlign).countTrailingZeros(); + const MemRegion *Base = ER->getSuperRegion(); int BaseTZC = getTrailingZerosCount(Base, State, ASTCtx); + if (BaseTZC < 0) + return ElTyTZ > 0 ? ElTyTZ : -1; + int IdxTZC = getTrailingZerosCount(ER->getIndex(), State, ASTCtx); - if (BaseTZC >= 0) { - if (IdxTZC < 0 && NaturalAlign == 1) - return -1; - int ElemTyTZC = - llvm::APSInt::getUnsigned(NaturalAlign).countTrailingZeros(); - return std::min(BaseTZC, std::max(IdxTZC, 0) + ElemTyTZC); - } - return -1; + if (IdxTZC < 0 && NaturalAlign == 1) + return -1; + + return std::min(BaseTZC, std::max(IdxTZC, 0) + ElTyTZ); } unsigned AlignAttrVal = 0; @@ -247,7 +248,20 @@ void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, int DstTZC = getTrailingZerosCount(CE, C); int SrcTZC = getTrailingZerosCount(CE->getSubExpr(), C); - if (DstTZC < SrcTZC) { + ASTContext &ASTCtx = C.getASTContext(); + int DstReqTZC = -1; + if (CE->getType()->isPointerType()) { + if (!isGenericPointerType(CE->getType(), true)) { + const QualType &DstPTy = CE->getType()->getPointeeType(); + if (!DstPTy->isIncompleteType()) { + unsigned ReqAl = ASTCtx.getTypeAlignInChars(DstPTy).getQuantity(); + DstReqTZC = llvm::APSInt::getUnsigned(ReqAl).countTrailingZeros(); + } + } + } + + int NewAlign = std::max(SrcTZC, DstReqTZC); + if (DstTZC < NewAlign) { SVal DstVal = C.getSVal(CE); ProgramStateRef State = C.getState(); if (DstVal.isUnknown()) { @@ -257,7 +271,7 @@ void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, State = State->BindExpr(CE, LCtx, DstVal); } if (SymbolRef Sym = DstVal.getAsSymbol()) { - State = State->set(Sym, SrcTZC); + State = State->set(Sym, NewAlign); C.addTransition(State); } } diff --git a/clang/test/Analysis/pointer-alignment.c b/clang/test/Analysis/pointer-alignment.c index 3c4e7aac637a..283f6db850c9 100644 --- a/clang/test/Analysis/pointer-alignment.c +++ b/clang/test/Analysis/pointer-alignment.c @@ -4,6 +4,7 @@ typedef __uintcap_t uintptr_t; typedef __intcap_t intptr_t; typedef __typeof__(sizeof(int)) size_t; +extern void * malloc(size_t); double a[2048], // expected-note{{Original allocation of type 'double[2048]'}} *next = a; @@ -77,4 +78,21 @@ int voidptr_cast(int *ip1, int *ip2) { return b1 || b2; } +typedef struct B { + long *ptr; + long flex[1]; // expected-note{{Original allocation}} +} B; + +B* blob(size_t n) { + size_t s = sizeof(B) + n * sizeof(long) + n * sizeof(B); + B *p = malloc(s); + p->ptr = (long*)&p[1]; + return (B*)(&p->ptr[n]); // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'B * __capability' with required capability alignment 16 bytes}} +} +B* flex(size_t n) { + size_t s = sizeof(B) + (n-1) * sizeof(long) + n * sizeof(B); + B *p = malloc(s); + return (B*)(&p->flex[n]); // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'B * __capability' with required capability alignment 16 bytes}} +} + From d6144fc67815e3719b616e71566baaae07d133b4 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 10 Oct 2023 16:30:16 +0100 Subject: [PATCH 37/77] [CHERI_CSA] PointerAlignmentChecker: use declaration as uniquing location --- .../Checkers/PointerAlignmentChecker.cpp | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index a554152d46ae..b932cb351e5b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -29,12 +29,12 @@ //===----------------------------------------------------------------------===// #include "CHERI/CHERIUtils.h" +#include #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include #include #include +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" using namespace clang; using namespace ento; @@ -425,11 +425,18 @@ void printAlign(raw_ostream &OS, unsigned TZC) { OS << ")"; } -void describeOriginalAllocation(const MemRegion *MR, PathSensitiveBugReport &W, - const SourceManager &SM, +const DeclRegion *getOriginalAllocation(const MemRegion *MR) { + if (const DeclRegion *DR = MR->getAs()) + return DR; + if (const ElementRegion *ER = MR->getAs()) + return getOriginalAllocation(ER->getSuperRegion()); + return nullptr; +} + +void describeOriginalAllocation(const ValueDecl *SrcDecl, + PathDiagnosticLocation SrcLoc, + PathSensitiveBugReport &W, ASTContext &ASTCtx) { - if (const DeclRegion *DR = MR->getAs()) { - const ValueDecl *SrcDecl = DR->getDecl(); SmallString<350> Note; llvm::raw_svector_ostream OS2(Note); const QualType &AllocType = SrcDecl->getType().getCanonicalType(); @@ -438,9 +445,7 @@ void describeOriginalAllocation(const MemRegion *MR, PathSensitiveBugReport &W, OS2 << " which has an alignment requirement "; OS2 << ASTCtx.getTypeAlignInChars(AllocType).getQuantity(); OS2 << " bytes"; - W.addNote(Note, PathDiagnosticLocation::create(SrcDecl, SM)); - } else if (const ElementRegion *ER = MR->getAs()) - describeOriginalAllocation(ER->getSuperRegion(), W, SM, ASTCtx); + W.addNote(Note, SrcLoc); } } // namespace @@ -467,16 +472,27 @@ PointerAlignmentChecker::emitCastAlignWarn( OS << " alignment " << DstReqAlign; OS << " bytes"; + const SVal &SrcVal = C.getSVal(CE->getSubExpr()); + const ValueDecl *MRDecl = nullptr; + PathDiagnosticLocation MRDeclLoc; + if (const MemRegion *MR = SrcVal.getAsRegion()) { + if (const DeclRegion *OriginalAlloc = getOriginalAllocation(MR)) { + MRDecl = OriginalAlloc->getDecl(); + MRDeclLoc = PathDiagnosticLocation::create(MRDecl, C.getSourceManager()); + } + } + auto W = std::make_unique( - DstAlignIsCap ? *CapCastAlignBug : *CastAlignBug, ErrorMessage, ErrNode); - W->addRange(CE->getSourceRange()); + DstAlignIsCap ? *CapCastAlignBug : *CastAlignBug, + ErrorMessage, ErrNode, + MRDeclLoc, MRDecl); - const SVal &SrcVal = C.getSVal(CE->getSubExpr()); W->markInteresting(SrcVal); if (SymbolRef S = SrcVal.getAsSymbol()) W->addVisitor(std::make_unique(S)); - else if (const MemRegion *MR = SrcVal.getAsRegion()) { - describeOriginalAllocation(MR, *W, C.getSourceManager(), C.getASTContext()); + + if (MRDecl) { + describeOriginalAllocation(MRDecl, MRDeclLoc, *W, C.getASTContext()); } C.emitReport(std::move(W)); From b08eede73b893fc1b51d139bf4beb0bf36590ebe Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 1 Nov 2023 15:00:05 +0000 Subject: [PATCH 38/77] [CHERI_CSA] CapabilityCopyChecker: fix infinite recursion --- .../lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index 352c84513b43..6a0016b4ac7c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -125,7 +125,7 @@ const MemRegion *stripNonCapShift(const MemRegion *R, ASTContext &ASTCtx) { if (!isNonCapScalarType(ER->getValueType(), ASTCtx)) return R; - return ER->getSuperRegion(); + return stripNonCapShift(ER->getSuperRegion(), ASTCtx); } bool isVoidOrCharPtrArgRegion(const MemRegion *Reg, bool AcceptCharPtr) { From f8be53f795669b33e3d777cc1a395a456b1ec82d Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 20 Oct 2023 14:59:13 +0100 Subject: [PATCH 39/77] [CHERI_CSA] PointerSizeAssumptionsChecker: new checker --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../CHERI/PointerSizeAssumptionsChecker.cpp | 127 ++++++++++++++++++ .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../Analysis/Checkers/CHERI/assume-ptr-size.c | 64 +++++++++ 4 files changed, 196 insertions(+) create mode 100644 clang/lib/StaticAnalyzer/Checkers/CHERI/PointerSizeAssumptionsChecker.cpp create mode 100644 clang/test/Analysis/Checkers/CHERI/assume-ptr-size.c diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 3986433db544..8d9e80a2219f 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1772,6 +1772,10 @@ def CapabilityCopyChecker : Checker<"CapabilityCopy">, ]>, Documentation; +def PointerSizeAssumptionsChecker : Checker<"PointerSizeAssumptions">, + HelpText<"Detect hardcoded expectations on pointer sizes">, + Documentation; + } // end cheri let ParentPackage = CHERIAlpha in { diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/PointerSizeAssumptionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/PointerSizeAssumptionsChecker.cpp new file mode 100644 index 000000000000..934aa0f10c14 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/PointerSizeAssumptionsChecker.cpp @@ -0,0 +1,127 @@ +// ==-- PointerSizeAssumptionsChecker.cpp -------------------------*- 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 checker detects code where the pointer size was checked against +// a constant, but the case of capabilities was not addressed explicitly. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/raw_ostream.h" + +#include "CHERIUtils.h" + +using namespace clang; +using namespace ento; +using namespace cheri; +using namespace ast_matchers; + +namespace { + +// ID of a node at which the diagnostic would be emitted. +constexpr llvm::StringLiteral WarnAtNode = "if_stmt"; +constexpr llvm::StringLiteral ConstNode = "size_const"; +constexpr llvm::StringLiteral TypeNode = "sizeof_type"; + +class PointerSizeAssumptionsChecker : public Checker { +public: + void checkASTCodeBody(const Decl *D, AnalysisManager &mgr, + BugReporter &BR) const; +}; + +auto matchCheckPtrSize() -> decltype(decl()) { + // sizeof(void*) ... + auto PtrSize = sizeOfExpr(has(expr(hasType(pointerType())).bind("ptr"))); + + // ... == 8 + auto SizeConst = ignoringImplicit(integerLiteral().bind(ConstNode)); + + // ... == sizeof long_var + auto SizeofFTy = + sizeOfExpr(unless(has(expr(hasType(pointerType()))))).bind(TypeNode); + + auto Size = expr(anyOf(SizeConst, SizeofFTy)); + + auto Check = binaryOperation(isComparisonOperator(), has(PtrSize), has(Size)); + auto PointerSizeCheck = traverse( + TK_AsIs, + stmt(ifStmt(hasCondition(Check))).bind(WarnAtNode)); + + return decl(forEachDescendant(PointerSizeCheck)); +} + +static void emitDiagnostics(const Expr *WarnStmt, const Decl *D, + BugReporter &BR, AnalysisManager &AM, + const PointerSizeAssumptionsChecker *Checker) { + auto *ADC = AM.getAnalysisDeclContext(D); + + auto Range = WarnStmt->getSourceRange(); + auto Location = PathDiagnosticLocation::createBegin(WarnStmt, + BR.getSourceManager(), + ADC); + std::string Diagnostics; + llvm::raw_string_ostream OS(Diagnostics); + + ASTContext &ASTCtx = AM.getASTContext(); + OS << "This code may fail to consider the case of " + << ASTCtx.toBits(getCapabilityTypeSize(ASTCtx)) << "-bit " + << "pointers"; + + BR.EmitBasicReport(ADC->getDecl(), Checker, + "Only a limited number of pointer sizes checked", + "CHERI portability", + OS.str(), Location, Range); +} + +void PointerSizeAssumptionsChecker::checkASTCodeBody(const Decl *D, + AnalysisManager &AM, + BugReporter &BR) const { + auto MatcherM = matchCheckPtrSize(); + + ASTContext &ASTCtx = AM.getASTContext(); + const unsigned CAP_SIZEOF = getCapabilityTypeSize(ASTCtx).getQuantity(); + auto Matches = match(MatcherM, *D, ASTCtx); + const IfStmt *WarnStmt = nullptr; + for (const auto &Match : Matches) { + unsigned s; + if (const IntegerLiteral *IL = Match.getNodeAs(ConstNode)) { + s = IL->getValue().getZExtValue(); + } else { + const UnaryExprOrTypeTraitExpr *SizeOfExpr = + Match.getNodeAs(TypeNode); + assert(SizeOfExpr); + s = AM.getASTContext() + .getTypeSizeInChars(SizeOfExpr->getTypeOfArgument()) + .getQuantity(); + } + if (s == CAP_SIZEOF) + return; + if (!WarnStmt) + WarnStmt = Match.getNodeAs(WarnAtNode); + } + if (WarnStmt) + emitDiagnostics(WarnStmt->getCond(), D, BR, AM, this); +} + +} // namespace + +void ento::registerPointerSizeAssumptionsChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterPointerSizeAssumptionsChecker( + const CheckerManager &mgr) { + return true; +} \ No newline at end of file diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index b51e17e7ef48..887c1eb40f07 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -25,6 +25,7 @@ add_clang_library(clangStaticAnalyzerCheckers CheckSizeofPointer.cpp CheckerDocumentation.cpp CHERI/CHERIUtils.cpp + CHERI/PointerSizeAssumptionsChecker.cpp CHERI/ProvenanceSourceChecker.cpp CHERI/CapabilityCopyChecker.cpp ChrootChecker.cpp diff --git a/clang/test/Analysis/Checkers/CHERI/assume-ptr-size.c b/clang/test/Analysis/Checkers/CHERI/assume-ptr-size.c new file mode 100644 index 000000000000..c9349d75f710 --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/assume-ptr-size.c @@ -0,0 +1,64 @@ +// RUN: %cheri_purecap_cc1 -analyze -verify %s \ +// RUN: -analyze -analyzer-checker=core,cheri.PointerSizeAssumptions + + +typedef __intcap_t intptr_t; +typedef __typeof__(sizeof(int)) size_t; +extern void *memcpy(void *dest, const void *src, size_t n); + +void *f(long i64) { + void *p; + if (sizeof(i64) == sizeof(p)) { // expected-warning{{This code may fail to consider the case of 128-bit pointers}} + memcpy(&p, &i64, sizeof(p)); + } else { + int i32 = i64 & 0xffffffffL; + memcpy(&p, &i32, sizeof(p)); + } + return p; +} + +void *f2(long i64){ + void *p; + if( sizeof(i64)==i64 ){ + memcpy(&p, &i64, sizeof(p)); + }else{ + int i32 = i64 & 0xffffffffL; + memcpy(&p, &i32, sizeof(p)); + } + return p; +} + +void *f3(long i64){ + void *p; + if( sizeof(p)==8 ){ // expected-warning{{This code may fail to consider the case of 128-bit pointers}} + memcpy(&p, &i64, sizeof(p)); + }else{ + int i32 = i64 & 0xffffffffL; + memcpy(&p, &i32, sizeof(p)); + } + return p; +} + +void *f4(long i64){ + void *p; + if( sizeof(p)==sizeof(long) ){ // expected-warning{{This code may fail to consider the case of 128-bit pointers}} + memcpy(&p, &i64, sizeof(p)); + }else{ + int i32 = i64 & 0xffffffffL; + memcpy(&p, &i32, sizeof(p)); + } + return p; +} + +void *f5(long i64){ + void *p; + if( sizeof(p)==sizeof(i64) ){ // no warn + memcpy(&p, &i64, sizeof(p)); + }else if (sizeof(p)==sizeof(int)) { + int i32 = i64 & 0xffffffffL; + memcpy(&p, &i32, sizeof(p)); + } else if (sizeof(p)==sizeof(intptr_t)) { + p = 0; + } + return p; +} From 658efa9fcde51ed0dff6ce7584b88fd8a6a20470 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 13 Nov 2023 17:13:50 +0000 Subject: [PATCH 40/77] [CHERI_CSA] ProvenanceSourceChecker: divide bugs into subtypes Introduce new Bug Types for binary operation with ambiguous provenance: - LHS and RHS are pointers: CHERI-incompatible pointer arithmetic - LHS is integer, RHS is pointer: Capability derived from wrong argument --- .../CHERI/ProvenanceSourceChecker.cpp | 106 +++++++++++++----- .../Checkers/CHERI/provenance-source.c | 12 +- 2 files changed, 83 insertions(+), 35 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index af6cc7d684a7..46ea02eef128 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -28,8 +28,11 @@ class ProvenanceSourceChecker : public Checker, check::PostStmt, check::PostStmt, check::DeadSymbols> { - std::unique_ptr AmbiguousProvenanceBinOpBugType; - std::unique_ptr AmbiguousProvenancePtrBugType; + std::unique_ptr AmbigProvAddrArithBugType; + std::unique_ptr AmbigProvWrongOrderBugType; + std::unique_ptr AmbigProvBinOpBugType; + + std::unique_ptr AmbigProvAsPtrBugType; std::unique_ptr InvalidCapPtrBugType; std::unique_ptr PtrdiffAsIntCapBugType; @@ -81,6 +84,11 @@ class ProvenanceSourceChecker : public Checker, private: // Utility + const BugType &explainWarning(const BinaryOperator *BO, CheckerContext &C, + bool LHSIsAddr, bool LHSIsNullDerived, + bool RHSIsAddr, bool RHSIsNullDerived, + raw_ostream &OS) const; + ExplodedNode *emitAmbiguousProvenanceWarn(const BinaryOperator *BO, CheckerContext &C, bool LHSIsAddr, bool LHSIsNullDerived, @@ -104,11 +112,20 @@ REGISTER_SET_WITH_PROGRAMSTATE(AmbiguousProvenanceReg, const MemRegion *) REGISTER_TRAIT_WITH_PROGRAMSTATE(Ptr2IntCapId, unsigned) ProvenanceSourceChecker::ProvenanceSourceChecker() { - AmbiguousProvenanceBinOpBugType.reset( + AmbigProvBinOpBugType.reset( new BugType(this, "Binary operation with ambiguous provenance", "CHERI portability")); - AmbiguousProvenancePtrBugType.reset( + AmbigProvAddrArithBugType.reset( + new BugType(this, + "CHERI-incompatible pointer arithmetic", + "CHERI portability")); + AmbigProvWrongOrderBugType.reset( + new BugType(this, + "Capability derived from wrong argument", + "CHERI portability")); + + AmbigProvAsPtrBugType.reset( new BugType(this, "Capability with ambiguous provenance used as pointer", "CHERI portability")); @@ -231,7 +248,7 @@ void ProvenanceSourceChecker::checkPreStmt(const CastExpr *CE, if (!ErrNode) return; R = std::make_unique( - *AmbiguousProvenancePtrBugType, + *AmbigProvAsPtrBugType, "Capability with ambiguous provenance is used as pointer", ErrNode); } else if (hasNoProvenance(State, SrcVal) && !SrcVal.isConstant() && !isIntToVoidPtrCast(CE)) { @@ -303,47 +320,78 @@ FixItHint addFixIt(const Expr *NDOp, CheckerContext &C, bool IsUnsigned) { } // namespace -ExplodedNode *ProvenanceSourceChecker::emitAmbiguousProvenanceWarn( - const BinaryOperator *BO, CheckerContext &C, - bool LHSIsAddr, bool LHSIsNullDerived, - bool RHSIsAddr, bool RHSIsNullDerived) const { - SmallString<350> ErrorMessage; - llvm::raw_svector_ostream OS(ErrorMessage); +const BugType &ProvenanceSourceChecker::explainWarning( + const BinaryOperator *BO, CheckerContext &C, bool LHSIsAddr, + bool LHSIsNullDerived, bool RHSIsAddr, bool RHSIsNullDerived, + raw_ostream &OS) const { + OS << "Result of '"<< BO->getOpcodeStr() << "' on capability type '"; + BO->getType().print(OS, PrintingPolicy(C.getASTContext().getLangOpts())); + OS << "'; it is unclear which side should be used as the source of " + "provenance"; + + if (RHSIsAddr) { + OS << ". Note: along this path, "; + if (LHSIsAddr) { + OS << "LHS and RHS were derived from pointers." + " Result capability will be derived from LHS by default." + " This code may need to be rewritten for CHERI."; + return *AmbigProvAddrArithBugType; + } - const QualType &T = BO->getType(); - bool const IsUnsigned = T->isUnsignedIntegerType(); + if (LHSIsNullDerived) { + OS << "LHS was derived from NULL, RHS was derived from pointer." + " Currently, provenance is inherited from LHS," + " therefore result capability will be invalid."; + } else { // unknown LHS provenance + OS << "RHS was derived from pointer." + " Currently, provenance is inherited from LHS." + " Consider indicating the provenance-carrying argument " + " explicitly by casting the other argument to '" + << (BO->getType()->isUnsignedIntegerType() ? "size_t" : "ptrdiff_t") + << "'. "; + } + return *AmbigProvWrongOrderBugType; + } - OS << "Result of '"<< BO->getOpcodeStr() << "' on capability type '"; - T.print(OS, PrintingPolicy(C.getASTContext().getLangOpts())); - OS << "'; it is unclear which side should be used as the source " - "of provenance; consider indicating the provenance-carrying argument " + OS << "; consider indicating the provenance-carrying argument " "explicitly by casting the other argument to '" - << (IsUnsigned ? "size_t" : "ptrdiff_t") << "'. "; + << (BO->getType()->isUnsignedIntegerType() ? "size_t" : "ptrdiff_t") + << "'. Note: along this path, "; - OS << "Note: along this path, "; - if (LHSIsAddr && RHSIsAddr) - OS << "LHS and RHS were derived from pointers"; - else if (LHSIsNullDerived && RHSIsNullDerived) { + if (LHSIsNullDerived && RHSIsNullDerived) { OS << "LHS and RHS were derived from NULL"; } else { if (LHSIsAddr) OS << "LHS was derived from pointer"; if (LHSIsNullDerived) OS << "LHS was derived from NULL"; - if ((LHSIsAddr || LHSIsNullDerived) && (RHSIsAddr || RHSIsNullDerived)) - OS << ", "; - if (RHSIsAddr) - OS << "RHS was derived from pointer"; - if (RHSIsNullDerived) + if (RHSIsNullDerived) { + if (LHSIsAddr || LHSIsNullDerived) + OS << ", "; OS << "RHS was derived from NULL"; + } } + return *AmbigProvBinOpBugType; +} + +ExplodedNode *ProvenanceSourceChecker::emitAmbiguousProvenanceWarn( + const BinaryOperator *BO, CheckerContext &C, + bool LHSIsAddr, bool LHSIsNullDerived, + bool RHSIsAddr, bool RHSIsNullDerived) const { + bool const IsUnsigned = BO->getType()->isUnsignedIntegerType(); + + SmallString<350> ErrorMessage; + llvm::raw_svector_ostream OS(ErrorMessage); + + const BugType &BT = explainWarning(BO, C, LHSIsAddr, LHSIsNullDerived, + RHSIsAddr, RHSIsNullDerived, OS); + // Generate the report. ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); if (!ErrNode) return nullptr; - auto R = std::make_unique( - *AmbiguousProvenanceBinOpBugType, ErrorMessage, ErrNode); + auto R = std::make_unique(BT, ErrorMessage, ErrNode); R->addRange(BO->getSourceRange()); if (ShowFixIts) { diff --git a/clang/test/Analysis/Checkers/CHERI/provenance-source.c b/clang/test/Analysis/Checkers/CHERI/provenance-source.c index b2f124e89754..554a7d790eec 100644 --- a/clang/test/Analysis/Checkers/CHERI/provenance-source.c +++ b/clang/test/Analysis/Checkers/CHERI/provenance-source.c @@ -27,7 +27,7 @@ char right_prov(unsigned d, char *p) { uintptr_t a = d; uintptr_t b = (uintptr_t)p; - uintptr_t s = a + b; // expected-warning{{Result of '+' on capability type 'unsigned __intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'size_t'. Note: along this path, LHS was derived from NULL, RHS was derived from pointer}} + uintptr_t s = a + b; // expected-warning{{Result of '+' on capability type 'unsigned __intcap'; it is unclear which side should be used as the source of provenance. Note: along this path, LHS was derived from NULL, RHS was derived from pointer. Currently, provenance is inherited from LHS, therefore result capability will be invalid}} // expected-warning@-1{{binary expression on capability types 'uintptr_t' (aka 'unsigned __intcap') and 'uintptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} // CHECK-FIXES: intptr_t s = (ptrdiff_t)(a) + b; @@ -39,7 +39,7 @@ char both_prov(int* d, char *p) { intptr_t a = (intptr_t)d; intptr_t b = (intptr_t)p; - intptr_t s = a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS and RHS were derived from pointers}} + intptr_t s = a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance. Note: along this path, LHS and RHS were derived from pointers. Result capability will be derived from LHS by default. This code may need to be rewritten for CHERI}} // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} return *(char*)s; // expected-warning{{Capability with ambiguous provenance is used as pointer}} } @@ -64,7 +64,7 @@ char right_prov_cond(int d, char *p, int x) { b = x; intptr_t s = a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS and RHS were derived from NULL}} - // expected-warning@-1{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from NULL, RHS was derived from pointer}} + // expected-warning@-1{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance. Note: along this path, LHS was derived from NULL, RHS was derived from pointer. Currently, provenance is inherited from LHS, therefore result capability will be invalid}} // expected-warning@-2{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} return *(char*)s;// expected-warning{{Capability with ambiguous provenance is used as pointer}} @@ -152,7 +152,7 @@ intptr_t fp3(char *s1, char *s2) { intptr_t a __attribute__((cheri_no_provenance)); a = (intptr_t)s1; intptr_t b = (intptr_t)s2; - return a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS and RHS were derived from pointers}} FIXME: expect no warning + return a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance. Note: along this path, LHS and RHS were derived from pointers. Result capability will be derived from LHS by default. This code may need to be rewritten for CHERI}} FIXME: expect no warning } uintptr_t fp4(char *str, int f) { @@ -163,7 +163,7 @@ uintptr_t fn2(char *a, char *b) { uintptr_t msk = sizeof (long) - 1; uintptr_t x = (uintptr_t)a | (uintptr_t)b; - // expected-warning@-1{{Result of '|' on capability type 'unsigned __intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'size_t'. Note: along this path, LHS and RHS were derived from pointers}} + // expected-warning@-1{{Result of '|' on capability type 'unsigned __intcap'; it is unclear which side should be used as the source of provenance. Note: along this path, LHS and RHS were derived from pointers. Result capability will be derived from LHS by default. This code may need to be rewritten for CHERI}} // expected-warning@-2{{binary expression on capability types 'uintptr_t' (aka 'unsigned __intcap') and 'uintptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} int ma = x & msk; @@ -209,7 +209,7 @@ intptr_t foo(int d) { } intptr_t add(intptr_t a, intptr_t b) { - return a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance; consider indicating the provenance-carrying argument explicitly by casting the other argument to 'ptrdiff_t'. Note: along this path, LHS was derived from NULL, RHS was derived from pointer}} + return a + b; // expected-warning{{Result of '+' on capability type '__intcap'; it is unclear which side should be used as the source of provenance. Note: along this path, LHS was derived from NULL, RHS was derived from pointer. Currently, provenance is inherited from LHS, therefore result capability will be invalid}} // expected-warning@-1{{binary expression on capability types 'intptr_t' (aka '__intcap') and 'intptr_t'; it is not clear which should be used as the source of provenance; currently provenance is inherited from the left-hand side}} // CHECK-FIXES: return (ptrdiff_t)(a) + b; From 5bb88e11a22df9f6ec6a4cc77921daf540531310 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 15 Nov 2023 15:58:14 +0000 Subject: [PATCH 41/77] [CHERI_CSA] ProvenanceSource: suppress with -Wno-cheri-provenance If [-Wcheri-provenance] compiler warning is disabled, do not emit ambiguous provenance warning for cases when the default choice of LHS as a source of provenance will probably be fine. Binary operations with provenance-carrying RHS are reported anyway, as this may indicate a real bug. --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 9 ++++++++- clang/lib/Driver/ToolChains/Clang.cpp | 16 ++++++++++++++-- .../Checkers/CHERI/ProvenanceSourceChecker.cpp | 17 +++++++++++++++-- clang/test/Analysis/analyzer-config.c | 1 + 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 8d9e80a2219f..742c10703ad0 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1755,7 +1755,14 @@ def ProvenanceSourceChecker : Checker<"ProvenanceSource">, "ShowFixIts", "Enable fix-it hints for this checker", "false", - InAlpha> + InAlpha>, + CmdLineOption ]>, Documentation; diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 9b20e4e4941d..050b25c097a8 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -26,6 +26,7 @@ #include "clang/Basic/CLWarnings.h" #include "clang/Basic/CharInfo.h" #include "clang/Basic/CodeGenOptions.h" +#include #include "clang/Basic/LangOptions.h" #include "clang/Basic/MakeSupport.h" #include "clang/Basic/ObjCRuntime.h" @@ -3191,7 +3192,8 @@ static void RenderFloatingPointOptions(const ToolChain &TC, const Driver &D, static void RenderAnalyzerOptions(const ArgList &Args, ArgStringList &CmdArgs, const llvm::Triple &Triple, - const InputInfo &Input) { + const InputInfo &Input, + DiagnosticsEngine &Diags) { // Add default argument set. if (!Args.hasArg(options::OPT__analyzer_no_default_checks)) { CmdArgs.push_back("-analyzer-checker=core"); @@ -3243,6 +3245,16 @@ static void RenderAnalyzerOptions(const ArgList &Args, ArgStringList &CmdArgs, (Triple.isMIPS() && tools::mips::hasMipsAbiArg(Args, "purecap")) || (Triple.isRISCV() && tools::riscv::isCheriPurecap(Args, Triple))) { CmdArgs.push_back("-analyzer-checker=cheri"); + + // disable AmbiguousProvenance war if [-Wcheri-provenance] is disabled + if (Diags.getDiagnosticLevel( + diag::warn_ambiguous_provenance_capability_binop, + SourceLocation()) < DiagnosticsEngine::Warning) { + CmdArgs.push_back("-analyzer-config"); + CmdArgs.push_back( + "cheri.ProvenanceSource:ReportForAmbiguousProvenance=false"); + } + CmdArgs.push_back("-analyzer-checker=optin.portability.PointerAlignment"); CmdArgs.push_back("-analyzer-checker=alpha.core.PointerSub"); } @@ -4996,7 +5008,7 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, CmdArgs.push_back("-DUNICODE"); if (isa(JA)) - RenderAnalyzerOptions(Args, CmdArgs, Triple, Input); + RenderAnalyzerOptions(Args, CmdArgs, Triple, Input, D.getDiags()); if (isa(JA) || (isa(JA) && Args.hasArg(options::OPT__analyze))) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index 46ea02eef128..758876b72813 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -13,10 +13,11 @@ #include "CHERIUtils.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" -#include #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include +#include using namespace clang; using namespace ento; @@ -81,6 +82,7 @@ class ProvenanceSourceChecker : public Checker, void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; bool ShowFixIts = false; + bool ReportForAmbiguousProvenance = true; private: // Utility @@ -379,8 +381,8 @@ ExplodedNode *ProvenanceSourceChecker::emitAmbiguousProvenanceWarn( const BinaryOperator *BO, CheckerContext &C, bool LHSIsAddr, bool LHSIsNullDerived, bool RHSIsAddr, bool RHSIsNullDerived) const { - bool const IsUnsigned = BO->getType()->isUnsignedIntegerType(); + bool const IsUnsigned = BO->getType()->isUnsignedIntegerType(); SmallString<350> ErrorMessage; llvm::raw_svector_ostream OS(ErrorMessage); @@ -485,6 +487,14 @@ void ProvenanceSourceChecker::checkPostStmt(const BinaryOperator *BO, ExplodedNode *N; bool InvalidCap; if (LHSActiveProv && RHSActiveProv && !IsSub) { + if (!RHSIsAddr && !this->ReportForAmbiguousProvenance) { + // No strong evidence of a real bug here. This code will probably + // be fine with the default choice of LHS for capability derivation. + // Don't report if [-Wcheri-provenance] compiler warning is disabled + // and don't propagate ambiguous provenance flag further + return; + } + N = emitAmbiguousProvenanceWarn(BO, C, LHSIsAddr, LHSIsNullDerived, RHSIsAddr, RHSIsNullDerived); if (!N) @@ -649,6 +659,9 @@ void ento::registerProvenanceSourceChecker(CheckerManager &mgr) { auto *Checker = mgr.registerChecker(); Checker->ShowFixIts = mgr.getAnalyzerOptions().getCheckerBooleanOption( Checker, "ShowFixIts"); + Checker->ReportForAmbiguousProvenance = + mgr.getAnalyzerOptions().getCheckerBooleanOption( + Checker, "ReportForAmbiguousProvenance"); } bool ento::shouldRegisterProvenanceSourceChecker(const CheckerManager &Mgr) { diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index 7a6bc7831a2e..e9683063172c 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -34,6 +34,7 @@ // CHECK-NEXT: cfg-scopes = false // CHECK-NEXT: cfg-temporary-dtors = true // CHECK-NEXT: cheri.CapabilityCopy:ReportForCharPtr = true +// CHECK-NEXT: cheri.ProvenanceSource:ReportForAmbiguousProvenance = true // CHECK-NEXT: cheri.ProvenanceSource:ShowFixIts = false // CHECK-NEXT: consider-single-element-arrays-as-flexible-array-members = false // CHECK-NEXT: core.CallAndMessage:ArgInitializedness = true From dc3ca058a6d0aa0a33df871986d7313ea099d448 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 12 Feb 2024 14:02:37 +0000 Subject: [PATCH 42/77] [CHERI_CSA] Fix note links in reports HTML --- clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index b4578385a147..ec4305cef3dd 100644 --- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -585,7 +585,7 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, << D.getVerboseDescription() << "\n"; // The navigation across the extra notes pieces. - unsigned NumExtraPieces = 0; + unsigned NumExtraPieces = 1; for (const auto &Piece : path) { if (const auto *P = dyn_cast(Piece.get())) { int LineNumber = From 3f77b58b2b8115ac02d0edde05d0691efef1b42d Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 28 Nov 2023 17:23:01 +0000 Subject: [PATCH 43/77] [CHERI_CSA] Fix crash with FieldDecl as UniqLoc --- clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index d35646bfba91..840d2fcd1ab7 100644 --- a/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -748,10 +748,10 @@ void PlistDiagnostics::FlushDiagnosticsImpl( // the leak location even after code is added between the allocation // site and the end of scope (leak report location). if (UPDLoc.isValid()) { - FullSourceLoc UFunL( - SM.getExpansionLoc( - D->getUniqueingDecl()->getBody()->getBeginLoc()), - SM); + const Decl *UD = D->getUniqueingDecl(); + SourceLocation Loc = UD->hasBody() ? UD->getBody()->getBeginLoc() + : UD->getBeginLoc(); + FullSourceLoc UFunL(SM.getExpansionLoc(Loc), SM); o << " issue_hash_function_offset" << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber() << "\n"; From c129ebe105c3305eac698b53b230f897b522c112 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 27 Nov 2023 14:08:07 +0000 Subject: [PATCH 44/77] [CHERI_CSA] PointerAlignmentChecker: report implicit assignment amd memcpy 1. Track memory regions containing capabilities 2. Report memcpy to/from capability containing region (or void* generic storage regions) from/to underaligned memory objects 3. Report binding (e.g. assignment inside function call) of underaligned pointers to memory regions that hold capability-aligned pointers or void* pointers --- .../Checkers/CHERI/CHERIUtils.cpp | 17 + .../Checkers/CHERI/CHERIUtils.h | 2 + .../Checkers/PointerAlignmentChecker.cpp | 509 +++++++++++++++--- clang/test/Analysis/pointer-alignment.c | 79 ++- 4 files changed, 508 insertions(+), 99 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp index bebb553ea0ea..57594e9e819b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp @@ -35,6 +35,23 @@ bool isGenericPointerType(const QualType T, bool AcceptCharPtr) { T->getPointeeType()->isCharType()); } +bool hasCapability(const QualType OrigTy, ASTContext &Ctx) { + QualType Ty = OrigTy.getCanonicalType(); + if (Ty->isCHERICapabilityType(Ctx, true)) + return true; + if (const auto *Record = dyn_cast(Ty)) { + for (const auto *Field : Record->getDecl()->fields()) { + if (hasCapability(Field->getType(), Ctx)) + return true; + } + return false; + } + if (const auto *Array = dyn_cast(Ty)) { + return hasCapability(Array->getElementType(), Ctx); + } + return false; +} + } // end of namespace: cheri } // end of namespace: ento } // end of namespace: clang \ No newline at end of file diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h index 8aeecb4881d9..372a2fcfc447 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h @@ -25,6 +25,8 @@ CharUnits getCapabilityTypeAlign(ASTContext &ASTCtx); bool isGenericPointerType(const QualType T, bool AcceptCharPtr = true); +bool hasCapability(const QualType OrigTy, ASTContext &Ctx); + } // end of namespace: cheri } // end of namespace: ento } // end of namespace: clang diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index b932cb351e5b..9699bb73cfd9 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -29,12 +29,13 @@ //===----------------------------------------------------------------------===// #include "CHERI/CHERIUtils.h" -#include #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include #include +#include #include -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" using namespace clang; using namespace ento; @@ -43,10 +44,19 @@ using namespace cheri; namespace { class PointerAlignmentChecker : public Checker, check::PostStmt, - check::PostStmt, check::DeadSymbols> { + check::PostStmt, check::Bind, + check::PreCall, check::DeadSymbols> { std::unique_ptr CastAlignBug; std::unique_ptr CapCastAlignBug; + std::unique_ptr GenPtrEscapeAlignBug; + std::unique_ptr MemcpyAlignBug; + + const CallDescriptionMap> MemCpyFn { + {{"memcpy", 3}, {0, 1}}, + {{"mempcpy", 3}, {0, 1}}, + {{"memmove", 3}, {0, 1}}, + }; public: PointerAlignmentChecker(); @@ -54,13 +64,16 @@ class PointerAlignmentChecker void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; void checkPostStmt(const CastExpr *BO, CheckerContext &C) const; void checkPreStmt(const CastExpr *BO, CheckerContext &C) const; + void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; private: - ExplodedNode *emitCastAlignWarn(CheckerContext &C, unsigned SrcAlign, - unsigned DstReqAlign, - const CastExpr *CE) const; + ExplodedNode *emitAlignmentWarning(CheckerContext &C, const SVal &UnderalignedPtrVal, + const BugType &BT, + StringRef ErrorMessage, + const ValueDecl *CapSrcDecl = nullptr) const; class AlignmentBugVisitor : public BugReporterVisitor { public: @@ -83,6 +96,7 @@ class PointerAlignmentChecker } // namespace REGISTER_MAP_WITH_PROGRAMSTATE(TrailingZerosMap, SymbolRef, int) +REGISTER_SET_WITH_PROGRAMSTATE(CapStorageSet, const MemRegion*) PointerAlignmentChecker::PointerAlignmentChecker() { CastAlignBug.reset(new BugType(this, @@ -91,10 +105,35 @@ PointerAlignmentChecker::PointerAlignmentChecker() { CapCastAlignBug.reset(new BugType(this, "Cast increases required alignment to capability alignment", "CHERI portability")); + GenPtrEscapeAlignBug.reset(new BugType(this, + "Not capability-aligned pointer stored as 'void*'", + "CHERI portability")); + MemcpyAlignBug.reset(new BugType(this, + "Copying capabilities through underaligned memory", + "CHERI portability")); } namespace { +Optional globalOrParamPointeeType(SymbolRef Sym) { + const MemRegion *BaseRegOrigin = Sym->getOriginRegion(); + if (!BaseRegOrigin) + return llvm::None; + + if (!BaseRegOrigin->getMemorySpace()->hasGlobalsOrParametersStorage()) + return llvm::None; + + const QualType &SymTy = Sym->getType(); + if (SymTy->isPointerType()) { + const QualType &PT = SymTy->getPointeeType(); + if (!PT->isIncompleteType()) { + return PT; + } + } + + return llvm::None; +} + int getTrailingZerosCount(const SVal &V, ProgramStateRef State, ASTContext &ASTCtx); @@ -105,16 +144,11 @@ int getTrailingZerosCount(SymbolRef Sym, ProgramStateRef State, return *Align; // Is function argument or global? - if (const MemRegion *BaseRegOrigin = Sym->getOriginRegion()) - if (BaseRegOrigin->getMemorySpace()->hasGlobalsOrParametersStorage()) { - const QualType &SymTy = Sym->getType(); - if (SymTy->isPointerType() && !isGenericPointerType(SymTy)) { - const QualType &PT = SymTy->getPointeeType(); - if (!PT->isIncompleteType()) { - unsigned A = ASTCtx.getTypeAlignInChars(PT).getQuantity(); - return llvm::APSInt::getUnsigned(A).countTrailingZeros(); - } - } + Optional GlobalPointeeTy = globalOrParamPointeeType(Sym); + if (GlobalPointeeTy.hasValue()) { + QualType &PT = GlobalPointeeTy.getValue(); + unsigned A = ASTCtx.getTypeAlignInChars(PT).getQuantity(); + return llvm::APSInt::getUnsigned(A).countTrailingZeros(); } return -1; @@ -193,6 +227,69 @@ int getTrailingZerosCount(const Expr *E, CheckerContext &C) { return getTrailingZerosCount(V, C.getState(), C.getASTContext()); } +bool isCapabilityStorage(SymbolRef Sym, ASTContext &ASTCtx) { + const Optional GlobalPointeeTy = globalOrParamPointeeType(Sym); + if (GlobalPointeeTy.hasValue()) + return hasCapability(GlobalPointeeTy.getValue(), ASTCtx); + return false; +} + +bool isCapabilityStorage(const MemRegion *R, ProgramStateRef State, + ASTContext &ASTCtx) { + R = R->StripCasts(); + + if (State->contains(R)) + return true; + + if (const SymbolicRegion *SR = R->getAs()) + return isCapabilityStorage(SR->getSymbol(), ASTCtx); + + if (const TypedValueRegion *TR = R->getAs()) { + const QualType PT = TR->getDesugaredValueType(ASTCtx); + return hasCapability(PT, ASTCtx); + } + + return false; +} + +bool isCapabilityStorage(const SVal &V, ProgramStateRef State, + ASTContext &ASTCtx) { + if (const MemRegion *MR = V.getAsRegion()) { + return isCapabilityStorage(MR, State, ASTCtx); + } + if (SymbolRef Sym = V.getAsSymbol()) { + return isCapabilityStorage(Sym, ASTCtx); + } + return false; +} + +Optional getActualAlignment(CheckerContext &C, const SVal &SrcVal) { + if (SrcVal.isConstant()) // special value + return llvm::None; + + ASTContext &ASTCtx = C.getASTContext(); + int SrcTZC = getTrailingZerosCount(SrcVal, C.getState(), ASTCtx); + if (SrcTZC < 0) + return llvm::None; + + if ((unsigned)SrcTZC >= sizeof(unsigned int) * 8) + return llvm::None; // Too aligned, probably zero + return (1U << SrcTZC); +} + +Optional getRequiredAlignment(ASTContext &ASTCtx, + const QualType &PtrTy, + bool AssumeCapAlignForVoidPtr) { + if (!PtrTy->isPointerType()) + return llvm::None; + const QualType &PointeeTy = PtrTy->getPointeeType(); + if (!PointeeTy->isIncompleteType()) + return ASTCtx.getTypeAlignInChars(PointeeTy).getQuantity(); + else if (AssumeCapAlignForVoidPtr && isGenericPointerType(PtrTy, false)) + return getCapabilityTypeAlign(ASTCtx).getQuantity(); + return llvm::None; +} + /* Introduced by clang, not in C standard */ bool isImplicitConversionFromVoidPtr(const Stmt *S, CheckerContext &C) { using namespace clang::ast_matchers; @@ -204,6 +301,51 @@ bool isImplicitConversionFromVoidPtr(const Stmt *S, CheckerContext &C) { return !M.empty(); } +bool isGenericStorage(CheckerContext &C, const SVal &V) { + if (SymbolRef Sym = V.getAsSymbol()) { + if (!isGenericPointerType(Sym->getType(), false)) + return false; + if (const MemRegion *R = Sym->getOriginRegion()) { + const MemSpaceRegion *MS = R->getMemorySpace(); + if (isa(MS)) + return true; // global variable + if (auto SR = dyn_cast(MS)) + return SR->getStackFrame()->inTopFrame(); // top-level argument + + if (isa(R)) + return true; // struct field + } + } + return false; +} + +bool isGenericStorage(CheckerContext &C, const Expr *E) { + if (!isGenericPointerType(E->IgnoreImpCasts()->getType(), false)) + return false; + return isGenericStorage(C, C.getSVal(E)); +} + +static void describeObjectType(raw_ostream &OS, const QualType &Ty, + const LangOptions &LangOpts) { + if (Ty->isPointerType()) { + OS << " pointed by '"; + Ty.print(OS, PrintingPolicy(LangOpts)); + OS << "' pointer"; + } else { + OS << " of type '"; + Ty.print(OS, PrintingPolicy(LangOpts)); + OS << "'"; + } +} + +const DeclRegion *getOriginalAllocation(const MemRegion *MR) { + if (const DeclRegion *DR = MR->getAs()) + return DR; + if (const ElementRegion *ER = MR->getAs()) + return getOriginalAllocation(ER->getSuperRegion()); + return nullptr; +} + } // namespace void PointerAlignmentChecker::checkPreStmt(const CastExpr *CE, @@ -214,27 +356,218 @@ void PointerAlignmentChecker::checkPreStmt(const CastExpr *CE, if (isImplicitConversionFromVoidPtr(CE, C)) return; - const QualType &DstType = CE->getType(); - if (!DstType->isPointerType()) - return; - const QualType &DstPointeeTy = DstType->getPointeeType(); - if (DstPointeeTy->isIncompleteType()) + ASTContext &ASTCtx = C.getASTContext(); + + /* Calculate required alignment */ + const Optional &DstReqAlign = + getRequiredAlignment(ASTCtx, CE->getType(), false); + if (!DstReqAlign.hasValue()) return; + /* Calculate actual alignment */ const SVal &SrcVal = C.getSVal(CE->getSubExpr()); - if (SrcVal.isConstant()) - return; // special value - int SrcTZC = getTrailingZerosCount(SrcVal, C.getState(), C.getASTContext()); - if (SrcTZC < 0) + const Optional &SrcAlign = getActualAlignment(C, SrcVal); + if (!SrcAlign.hasValue()) + return; + + if (SrcAlign >= DstReqAlign) // OK + return; + + /* Emit warning */ + const QualType &DstTy = CE->getType(); + bool DstAlignIsCap = hasCapability(DstTy->getPointeeType(), ASTCtx); + const BugType &BT= DstAlignIsCap ? *CapCastAlignBug : *CastAlignBug; + + SmallString<350> ErrorMessage; + llvm::raw_svector_ostream OS(ErrorMessage); + OS << "Pointer value aligned to a " << SrcAlign << " byte boundary" + << " cast to type '" << DstTy.getAsString() << "'" + << " with " << DstReqAlign << "-byte"; + if (DstAlignIsCap) + OS << " capability"; + OS << " alignment"; + + emitAlignmentWarning(C, SrcVal, BT, ErrorMessage); +} + +void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, + CheckerContext &C) const { + ASTContext &ASTCtx = C.getASTContext(); + if (!isPureCapMode(ASTCtx)) + return; + + const BinaryOperator *BO = dyn_cast(S); + if (!BO || !BO->isAssignmentOp()) + return; + + const QualType &DstTy = BO->getLHS()->getType(); + if (!DstTy->isCHERICapabilityType(ASTCtx, true)) + return; + + bool DstIsCapStorage = false, DstIsGenericStorage = false; + + if (DstTy->isPointerType() && hasCapability(DstTy->getPointeeType(), ASTCtx)) + DstIsCapStorage = true; + + if (const MemRegion *MR = L.getAsRegion()) { + if (const TypedValueRegion *TR = MR->getAs()) { + const QualType &Ty = TR->getValueType(); + DstIsCapStorage |= Ty->isPointerType() && hasCapability(Ty->getPointeeType(), ASTCtx); + DstIsGenericStorage |= isGenericPointerType(Ty, false) && + (isa(TR->getMemorySpace()) || isa(TR->StripCasts())); + } + } + + if (SymbolRef Sym = L.getAsSymbol()) { + const QualType &SymTy = Sym->getType(); + if (SymTy->isPointerType()) { + const QualType &CopyTy = SymTy->getPointeeType(); + DstIsCapStorage |= CopyTy->isPointerType() && hasCapability(CopyTy->getPointeeType(), ASTCtx); + if (const MemRegion *R = Sym->getOriginRegion()) { + const MemSpaceRegion *MS = R->getMemorySpace(); + bool TopLevelArg = false; + if (auto SS = dyn_cast(MS)) + TopLevelArg = SS->getStackFrame()->inTopFrame(); + DstIsGenericStorage |= isGenericPointerType(CopyTy, false) && + (TopLevelArg || isa(MS) || isa(R->StripCasts())); + } + } + } + + if (!DstIsCapStorage && !DstIsGenericStorage) return; - if ((unsigned)SrcTZC >= sizeof(unsigned int)*8) - return; // Too aligned, probably a Zero - unsigned SrcAlign = (1U << SrcTZC); + /* Calculate actual alignment */ + unsigned CapAlign = getCapabilityTypeAlign(ASTCtx).getQuantity(); + const Optional &SrcAlign = getActualAlignment(C, V); + if (!SrcAlign.hasValue() || SrcAlign >= CapAlign) + return; + + /* Emit warning */ + SmallString<350> ErrorMessage; + llvm::raw_svector_ostream OS(ErrorMessage); + OS << "Pointer value aligned to a " << SrcAlign << " byte boundary"; + OS << " stored as type '" << BO->getLHS()->getType().getAsString() << "'"; + OS << ". Memory pointed by it"; + if (DstIsCapStorage) + OS << " is supposed to"; + else + OS << " may be used to"; + OS << " hold capabilities, for which " << CapAlign + << "-byte capability alignment will be required"; + + const ValueDecl *CapDstDecl = nullptr; + if (const MemRegion *SR = L.getAsRegion()) { + if (const DeclRegion *D = getOriginalAllocation(SR)) + CapDstDecl = D->getDecl(); + } + + emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, CapDstDecl); +} + +void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { ASTContext &ASTCtx = C.getASTContext(); - unsigned DstReqAlign = ASTCtx.getTypeAlignInChars(DstPointeeTy).getQuantity(); - if (SrcAlign < DstReqAlign) { - emitCastAlignWarn(C, SrcAlign, DstReqAlign, CE); + if (!isPureCapMode(ASTCtx)) + return; + + if (!Call.isGlobalCFunction()) + return; + + const std::pair *MemCpyParamPair = MemCpyFn.lookup(Call); + if (!MemCpyParamPair) + return; + + unsigned CapAlign = getCapabilityTypeAlign(ASTCtx).getQuantity(); + + /* Destination alignment */ + const Expr *DstExpr = Call.getArgExpr(MemCpyParamPair->first); + const QualType &DstTy = DstExpr->IgnoreImplicit()->getType(); + const SVal &DstVal = C.getSVal(DstExpr); + const Optional &DstCurAlign = getActualAlignment(C, DstVal); + bool DstIsCapStorage = isCapabilityStorage(DstVal, C.getState(), ASTCtx); + bool DstIsGenStorage = isGenericStorage(C, DstExpr); + + /* Source alignment */ + const Expr *SrcExpr = Call.getArgExpr(MemCpyParamPair->second); + const QualType &SrcTy = SrcExpr->IgnoreImplicit()->getType(); + const SVal &SrcVal = C.getSVal(SrcExpr); + const Optional &SrcCurAlign = getActualAlignment(C, SrcVal); + bool SrcIsCapStorage = isCapabilityStorage(SrcVal, C.getState(), ASTCtx); + bool SrcIsGenStorage = isGenericStorage(C, SrcExpr); + + if ((SrcIsCapStorage || SrcIsGenStorage) + && DstCurAlign.hasValue() && DstCurAlign < CapAlign) { + SmallString<350> ErrorMessage; + llvm::raw_svector_ostream OS(ErrorMessage); + OS << "Copied memory object"; + describeObjectType(OS, SrcTy, ASTCtx.getLangOpts()); + if (!SrcIsCapStorage) + OS << " may contain"; + else + OS << " contains"; + OS << " capabilities that require " + << CapAlign << "-byte capability alignment."; + OS << " Destination address alignment is " << DstCurAlign << "." + << " Storing a capability at an underaligned address" + " leads to tag stripping."; + + const ValueDecl *CapSrcDecl = nullptr; + if (const MemRegion *SR = SrcVal.getAsRegion()) { + if (const DeclRegion *D = getOriginalAllocation(SR)) + CapSrcDecl = D->getDecl(); + } + + emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, CapSrcDecl); + return; + } + + if ((DstIsCapStorage || DstIsGenStorage) + && SrcCurAlign.hasValue() && SrcCurAlign < CapAlign) { + SmallString<350> ErrorMessage; + llvm::raw_svector_ostream OS(ErrorMessage); + OS << "Destination memory object"; + describeObjectType(OS, DstTy, ASTCtx.getLangOpts()); + if (!DstIsCapStorage) + OS << " may"; + else + OS << " is supposed to"; + OS << " contain capabilities that require " + << CapAlign << "-byte capability alignment."; + OS << " Source address alignment is " << SrcCurAlign << ", which means" + " that copied object may have its capabilities tags" + " stripped earlier due to underaligned storage."; + + + const ValueDecl *CapSrcDecl = nullptr; + if (const MemRegion *SR = DstVal.getAsRegion()) { + if (const DeclRegion *D = getOriginalAllocation(SR)) + CapSrcDecl = D->getDecl(); + } + + emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, CapSrcDecl); + return; + } + + /* Propagate CapStorage flag */ + if (SrcIsCapStorage && !DstIsCapStorage) { + if (const MemRegion *R = DstVal.getAsRegion()) { + const ProgramStateRef State = + C.getState()->add(R->StripCasts()); + const NoteTag *Tag = + C.getNoteTag("Copied memory object contains capabilities"); + C.addTransition(State, C.getPredecessor(), Tag); + } + } + + if (!SrcIsCapStorage && DstIsCapStorage) { + if (const MemRegion *R = SrcVal.getAsRegion()) { + const ProgramStateRef State = + C.getState()->add(R->StripCasts()); + const NoteTag *Tag = + C.getNoteTag("Copied memory object should contain capabilities"); + C.addTransition(State, C.getPredecessor(), Tag); + } } } @@ -250,20 +583,26 @@ void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, ASTContext &ASTCtx = C.getASTContext(); int DstReqTZC = -1; + bool DstIsCapStorage = false; if (CE->getType()->isPointerType()) { if (!isGenericPointerType(CE->getType(), true)) { const QualType &DstPTy = CE->getType()->getPointeeType(); if (!DstPTy->isIncompleteType()) { unsigned ReqAl = ASTCtx.getTypeAlignInChars(DstPTy).getQuantity(); DstReqTZC = llvm::APSInt::getUnsigned(ReqAl).countTrailingZeros(); + DstIsCapStorage = hasCapability(DstPTy, ASTCtx); } } } + SVal DstVal = C.getSVal(CE); + SVal SrcVal = C.getSVal(CE->getSubExpr()); + ProgramStateRef State = C.getState(); + bool Updated = false; + + /* Update TrailingZerosMap */ int NewAlign = std::max(SrcTZC, DstReqTZC); if (DstTZC < NewAlign) { - SVal DstVal = C.getSVal(CE); - ProgramStateRef State = C.getState(); if (DstVal.isUnknown()) { const LocationContext *LCtx = C.getLocationContext(); DstVal = C.getSValBuilder().conjureSymbolVal( @@ -272,9 +611,24 @@ void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, } if (SymbolRef Sym = DstVal.getAsSymbol()) { State = State->set(Sym, NewAlign); - C.addTransition(State); + Updated = true; + } + } + + /* Update CapStorageSet */ + const ProgramPointTag *Tag = nullptr; + if ((isCapabilityStorage(SrcVal, State, ASTCtx) || DstIsCapStorage) + && !isCapabilityStorage(DstVal, State, ASTCtx)) { + if (const MemRegion *R = DstVal.getAsRegion()) { + State = State->add(R->StripCasts()); + Updated = true; + if (DstIsCapStorage) + Tag = C.getNoteTag("Cast to capability-containing type"); } } + + if (Updated) + C.addTransition(State, Tag); } bool valueIsLTPow2(const Expr *E, unsigned P, CheckerContext &C) { @@ -383,39 +737,24 @@ void PointerAlignmentChecker::checkPostStmt(const BinaryOperator *BO, void PointerAlignmentChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { + bool Updated = false; ProgramStateRef State = C.getState(); + TrailingZerosMapTy TZMap = State->get(); - bool Updated = false; - for (TrailingZerosMapTy::iterator I = TZMap.begin(), - E = TZMap.end(); I != E; ++I) { + for (TrailingZerosMapTy::iterator I = TZMap.begin(), E = TZMap.end(); + I != E; ++I) { if (SymReaper.isDead(I->first)) { State = State->remove(I->first); Updated = true; } } + if (Updated) C.addTransition(State); } namespace { -bool hasCapability(const QualType OrigTy, ASTContext &Ctx) { - QualType Ty = OrigTy.getCanonicalType(); - if (Ty->isCHERICapabilityType(Ctx, true)) - return true; - if (const auto *Record = dyn_cast(Ty)) { - for (const auto *Field : Record->getDecl()->fields()) { - if (hasCapability(Field->getType(), Ctx)) - return true; - } - return false; - } - if (const auto *Array = dyn_cast(Ty)) { - return hasCapability(Array->getElementType(), Ctx); - } - return false; -} - void printAlign(raw_ostream &OS, unsigned TZC) { OS << "aligned("; if (TZC < sizeof(unsigned long)*8) @@ -425,14 +764,6 @@ void printAlign(raw_ostream &OS, unsigned TZC) { OS << ")"; } -const DeclRegion *getOriginalAllocation(const MemRegion *MR) { - if (const DeclRegion *DR = MR->getAs()) - return DR; - if (const ElementRegion *ER = MR->getAs()) - return getOriginalAllocation(ER->getSuperRegion()); - return nullptr; -} - void describeOriginalAllocation(const ValueDecl *SrcDecl, PathDiagnosticLocation SrcLoc, PathSensitiveBugReport &W, @@ -448,53 +779,57 @@ void describeOriginalAllocation(const ValueDecl *SrcDecl, W.addNote(Note, SrcLoc); } +void describeCapabilityStorage(const ValueDecl *CapDecl, + PathDiagnosticLocation SrcLoc, + PathSensitiveBugReport &W, + ASTContext &ASTCtx) { + SmallString<350> Note; + llvm::raw_svector_ostream OS2(Note); + const QualType &AllocType = CapDecl->getType().getCanonicalType(); + OS2 << "Capabilities stored in "; + OS2 << "'" << AllocType.getAsString() << "'"; + W.addNote(Note, SrcLoc); +} + } // namespace ExplodedNode * -PointerAlignmentChecker::emitCastAlignWarn( - CheckerContext &C, unsigned SrcAlign, unsigned DstReqAlign, - const CastExpr *CE) const { +PointerAlignmentChecker::emitAlignmentWarning( + CheckerContext &C, + const SVal &UnderalignedPtrVal, + const BugType &BT, + StringRef ErrorMessage, + const ValueDecl *CapSrcDecl) const { ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); if (!ErrNode) return nullptr; - ASTContext &ASTCtx = C.getASTContext(); - const QualType &DstTy = CE->getType(); - bool DstAlignIsCap = hasCapability(DstTy->getPointeeType(), ASTCtx); - - SmallString<350> ErrorMessage; - llvm::raw_svector_ostream OS(ErrorMessage); - OS << "Pointer value aligned to a " << SrcAlign << " byte boundary"; - OS << " cast to type '" << DstTy.getAsString() << "'"; - OS << " with required"; - if (DstAlignIsCap) - OS << " capability"; - OS << " alignment " << DstReqAlign; - OS << " bytes"; - - const SVal &SrcVal = C.getSVal(CE->getSubExpr()); const ValueDecl *MRDecl = nullptr; PathDiagnosticLocation MRDeclLoc; - if (const MemRegion *MR = SrcVal.getAsRegion()) { + if (const MemRegion *MR = UnderalignedPtrVal.getAsRegion()) { if (const DeclRegion *OriginalAlloc = getOriginalAllocation(MR)) { MRDecl = OriginalAlloc->getDecl(); MRDeclLoc = PathDiagnosticLocation::create(MRDecl, C.getSourceManager()); } } - auto W = std::make_unique( - DstAlignIsCap ? *CapCastAlignBug : *CastAlignBug, - ErrorMessage, ErrNode, - MRDeclLoc, MRDecl); + auto W = std::make_unique(BT, ErrorMessage, ErrNode, + MRDeclLoc, MRDecl); - W->markInteresting(SrcVal); - if (SymbolRef S = SrcVal.getAsSymbol()) + W->markInteresting(UnderalignedPtrVal); + if (SymbolRef S = UnderalignedPtrVal.getAsSymbol()) W->addVisitor(std::make_unique(S)); if (MRDecl) { describeOriginalAllocation(MRDecl, MRDeclLoc, *W, C.getASTContext()); } + if (CapSrcDecl) { + auto CapSrcDeclLoc = + PathDiagnosticLocation::create(CapSrcDecl, C.getSourceManager()); + describeCapabilityStorage(CapSrcDecl, CapSrcDeclLoc, *W, C.getASTContext()); + } + C.emitReport(std::move(W)); return ErrNode; } diff --git a/clang/test/Analysis/pointer-alignment.c b/clang/test/Analysis/pointer-alignment.c index 283f6db850c9..75e33cf660a5 100644 --- a/clang/test/Analysis/pointer-alignment.c +++ b/clang/test/Analysis/pointer-alignment.c @@ -17,15 +17,15 @@ uintptr_t *u; void foo(void *v, int *pi, void *pv) { char *p0 = (char*)a; - *(void**)p0 = v; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'void * __capability * __capability' with required capability alignment 16 bytes}} + *(void**)p0 = v; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'void * __capability * __capability' with 16-byte capability alignment}} char *p1 = (char*)roundup2((uintptr_t)p0, sizeof(void*)); *(void**)p1 = v; // no warning char *p2 = p1 + 5*sizeof(double); - *(void**)p2 = v; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'void * __capability * __capability' with required capability alignment 16 bytes}} + *(void**)p2 = v; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'void * __capability * __capability' with 16-byte capability alignment}} - *(void**)pi = v; // expected-warning{{Pointer value aligned to a 4 byte boundary cast to type 'void * __capability * __capability' with required capability alignment 16 bytes}} + *(void**)pi = v; // expected-warning{{Pointer value aligned to a 4 byte boundary cast to type 'void * __capability * __capability' with 16-byte capability alignment}} *(void**)pv = v; // no warning - *(void**)next = v; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'void * __capability * __capability' with required capability alignment 16 bytes}} + *(void**)next = v; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'void * __capability * __capability' with 16-byte capability alignment}} if (u == NULL || u == MINUS_ONE) // no warning return; @@ -49,8 +49,8 @@ struct S { }; int struct_field(struct S *s) { uintptr_t* p1 = (uintptr_t*)&s->u[3]; // no warning - uintptr_t* p2 = (uintptr_t*)&s->i[8]; // expected-warning{{Pointer value aligned to a 4 byte boundary cast to type 'uintptr_t * __capability' with required capability alignment 16 bytes}} - uintptr_t* p3 = (uintptr_t*)&s->i_aligned[6]; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'uintptr_t * __capability' with required capability alignment 16 bytes}} + uintptr_t* p2 = (uintptr_t*)&s->i[8]; // expected-warning{{Pointer value aligned to a 4 byte boundary cast to type 'uintptr_t * __capability' with 16-byte capability alignment}} + uintptr_t* p3 = (uintptr_t*)&s->i_aligned[6]; // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'uintptr_t * __capability' with 16-byte capability alignment}} uintptr_t* p4 = (uintptr_t*)&s->i_aligned[4]; // no warning return (p4 - p3) + (p2 - p1); } @@ -59,21 +59,21 @@ void local_var(void) { char buf[4]; // expected-note{{Original allocation}} char buf_underaligned[4] __attribute__((aligned(2))); // expected-note{{Original allocation}} char buf_aligned[4] __attribute__((aligned(4))); - *(int*)buf = 42; // expected-warning{{Pointer value aligned to a 1 byte boundary cast to type 'int * __capability' with required alignment 4 bytes}} - *(int*)buf_underaligned = 42; // expected-warning{{Pointer value aligned to a 2 byte boundary cast to type 'int * __capability' with required alignment 4 bytes}} + *(int*)buf = 42; // expected-warning{{Pointer value aligned to a 1 byte boundary cast to type 'int * __capability' with 4-byte alignment}} + *(int*)buf_underaligned = 42; // expected-warning{{Pointer value aligned to a 2 byte boundary cast to type 'int * __capability' with 4-byte alignment}} *(int*)buf_aligned = 42; // no warning } char st_buf[4]; // expected-note{{Original allocation}} char st_buf_aligned[4] __attribute__((aligned(_Alignof(int*)))); void static_var(void) { - *(int*)st_buf = 42; // expected-warning{{Pointer value aligned to a 1 byte boundary cast to type 'int * __capability' with required alignment 4 bytes}} + *(int*)st_buf = 42; // expected-warning{{Pointer value aligned to a 1 byte boundary cast to type 'int * __capability' with 4-byte alignment}} *(int*)st_buf_aligned = 42; // no warning } int voidptr_cast(int *ip1, int *ip2) { intptr_t w = (intptr_t)(ip2) | 1; - int b1 = (ip1 == (int*)w); // expected-warning{{Pointer value aligned to a 1 byte boundary cast to type 'int * __capability' with required alignment 4 bytes}} + int b1 = (ip1 == (int*)w); // expected-warning{{Pointer value aligned to a 1 byte boundary cast to type 'int * __capability' with 4-byte alignment}} int b2 = (ip1 == (void*)w); // no-warn return b1 || b2; } @@ -87,12 +87,67 @@ B* blob(size_t n) { size_t s = sizeof(B) + n * sizeof(long) + n * sizeof(B); B *p = malloc(s); p->ptr = (long*)&p[1]; - return (B*)(&p->ptr[n]); // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'B * __capability' with required capability alignment 16 bytes}} + return (B*)(&p->ptr[n]); // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'B * __capability' with 16-byte capability alignment}} } B* flex(size_t n) { size_t s = sizeof(B) + (n-1) * sizeof(long) + n * sizeof(B); B *p = malloc(s); - return (B*)(&p->flex[n]); // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'B * __capability' with required capability alignment 16 bytes}} + return (B*)(&p->flex[n]); // expected-warning{{Pointer value aligned to a 8 byte boundary cast to type 'B * __capability' with 16-byte capability alignment}} } +char c_buf[100]; // expected-note{{Original allocation}} expected-note{{Original allocation}} expected-note{{Original allocation}} +void implicit_cap_storage(void **impl_cap_ptr) { + *impl_cap_ptr = &c_buf[0]; + // expected-warning@-1{{Pointer value aligned to a 1 byte boundary stored as type 'void * __capability'. Memory pointed by it may be used to hold capabilities, for which 16-byte capability alignment will be required}} +} + +char c_buf_aligned[100] __attribute__((aligned(_Alignof(void*)))); // expected-note{{Capabilities stored in 'char[100]'}} +extern void *memcpy(void *dest, const void *src, size_t n); +void copy_through_unaligned(intptr_t *src, void *dst, size_t n) { + void *s = src, *d = dst; + memcpy(c_buf_aligned, s, n * sizeof(intptr_t)); // no warn + memcpy(c_buf, c_buf_aligned, n * sizeof(intptr_t)); + // expected-warning@-1{{Copied memory object of type 'char[100]' contains capabilities that require 16-byte capability alignment. Destination address alignment is 1. Storing a capability at an underaligned address leads to tag stripping}} + memcpy(d, c_buf, n * sizeof(intptr_t)); + // expected-warning@-1{{Destination memory object pointed by 'void * __capability' pointer may contain capabilities that require 16-byte capability alignment. Source address alignment is 1, which means that copied object may have its capabilities tags stripped earlier due to underaligned storage}} +} + +// ---- +char a1[100], a2[100], a3[100], a4[100]; // expected-note{{Original allocation}} expected-note{{}} expected-note{{}} + +struct T { + void *p; +} gS; + +void copy(void *dst, void* src, size_t n) { + memcpy(dst, src, n); // no-warn +} + +void gen_storage(struct T *pT, void *p, size_t n) { + memcpy(a1, p, n); + //expected-warning@-1{{Copied memory object pointed by 'void * __capability' pointer may contain capabilities that require 16-byte capability alignment. Destination address alignment is 1. Storing a capability at an underaligned address leads to tag stripping}} + memcpy(pT->p, a2, n); + //expected-warning@-1{{Destination memory object pointed by 'void * __capability' pointer may contain capabilities that require 16-byte capability alignment. Source address alignment is 1, which means that copied object may have its capabilities tags stripped earlier due to underaligned storage}} + + struct T *mT = malloc(sizeof(struct T)); + memcpy(a3, mT->p, n); + //expected-warning@-1{{Copied memory object pointed by 'void * __capability' pointer may contain capabilities that require 16-byte capability alignment. Destination address alignment is 1. Storing a capability at an underaligned address leads to tag stripping}} + + void *m = malloc(n); + memcpy(a4, m, n); // no-warn + copy(a4, m, n); +} + +// ---- +char extra[100]; // expected-note{{Original allocation}} + +void alloc(void** p) { + *p = extra; + //expected-warning@-1{{Pointer value aligned to a 1 byte boundary stored as type 'void * __capability'. Memory pointed by it is supposed to hold capabilities, for which 16-byte capability alignment will be required}} +} + +void test(void) { + intptr_t *cp; // expected-note{{Capabilities stored}} + alloc((void**)&cp); +} From 2f7a26f578fb42ad87f9471100e0934dc908af9e Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 8 Dec 2023 18:16:46 +0000 Subject: [PATCH 45/77] [CHERI_CSA] PointerAlignmentChecker: fix FP for adjacent objects Do not propagate CapStorage attribute to shifted pointer values. This suppressed the false positive warning for adjacent objects of distinct types --- .../Checkers/PointerAlignmentChecker.cpp | 20 ++++++++++++++++--- clang/test/Analysis/pointer-alignment.c | 7 ++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index 9699bb73cfd9..bfc5dc67e987 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -571,6 +571,18 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, } } +namespace { + +bool isNonZeroShift(const SVal& V) { + if (const MemRegion *MR = V.getAsRegion()) + if (auto *ER = MR->getAs()) + if (!ER->getIndex().isZeroConstant()) + return true; + return false; +} + +} // namespace + void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, CheckerContext &C) const { CastKind CK = CE->getCastKind(); @@ -617,13 +629,15 @@ void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, /* Update CapStorageSet */ const ProgramPointTag *Tag = nullptr; - if ((isCapabilityStorage(SrcVal, State, ASTCtx) || DstIsCapStorage) - && !isCapabilityStorage(DstVal, State, ASTCtx)) { - if (const MemRegion *R = DstVal.getAsRegion()) { + if (!isCapabilityStorage(DstVal, State, ASTCtx)) { + if ((isCapabilityStorage(SrcVal, State, ASTCtx) && !isNonZeroShift(SrcVal)) + || DstIsCapStorage) { + if (const MemRegion *R = DstVal.getAsRegion()) { State = State->add(R->StripCasts()); Updated = true; if (DstIsCapStorage) Tag = C.getNoteTag("Cast to capability-containing type"); + } } } diff --git a/clang/test/Analysis/pointer-alignment.c b/clang/test/Analysis/pointer-alignment.c index 75e33cf660a5..03b21dbd33d5 100644 --- a/clang/test/Analysis/pointer-alignment.c +++ b/clang/test/Analysis/pointer-alignment.c @@ -112,6 +112,12 @@ void copy_through_unaligned(intptr_t *src, void *dst, size_t n) { // expected-warning@-1{{Destination memory object pointed by 'void * __capability' pointer may contain capabilities that require 16-byte capability alignment. Source address alignment is 1, which means that copied object may have its capabilities tags stripped earlier due to underaligned storage}} } +void after_cap_data(int n, int D) { + int **p = malloc(100); + p[0] = &((int*)&p[D])[0]; // 2D matrix + memcpy(c_buf, p[0], n); // no warn +} + // ---- char a1[100], a2[100], a3[100], a4[100]; // expected-note{{Original allocation}} expected-note{{}} expected-note{{}} @@ -150,4 +156,3 @@ void test(void) { intptr_t *cp; // expected-note{{Capabilities stored}} alloc((void**)&cp); } - From 66a2f5d2f85b6acce9cd2c0d19f5adf9abbf45b5 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 2 Feb 2024 15:52:34 +0000 Subject: [PATCH 46/77] [CHERI_CSA] PointerAlignmentChecker: fix FP for void* assignment Do not emit warning for assigning underaligned pointer to void* field if the content of the memory pointed by the former is known (and therefore should be already checked by analyzer for containing capabilities when it was stored at an underaligned address) --- .../Checkers/PointerAlignmentChecker.cpp | 79 +++++++++++-------- clang/test/Analysis/pointer-alignment.c | 4 +- 2 files changed, 47 insertions(+), 36 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index bfc5dc67e987..082b1df43aae 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -183,8 +183,8 @@ int getTrailingZerosCount(const MemRegion *R, ProgramStateRef State, } unsigned AlignAttrVal = 0; - if (auto DR = R->getAs()) { - if (auto AA = DR->getDecl()->getAttr()) { + if (const auto *DR = R->getAs()) { + if (auto *AA = DR->getDecl()->getAttr()) { if (!AA->isAlignmentDependent()) AlignAttrVal = AA->getAlignment(ASTCtx) / ASTCtx.getCharWidth(); } @@ -285,7 +285,7 @@ Optional getRequiredAlignment(ASTContext &ASTCtx, const QualType &PointeeTy = PtrTy->getPointeeType(); if (!PointeeTy->isIncompleteType()) return ASTCtx.getTypeAlignInChars(PointeeTy).getQuantity(); - else if (AssumeCapAlignForVoidPtr && isGenericPointerType(PtrTy, false)) + if (AssumeCapAlignForVoidPtr && isGenericPointerType(PtrTy, false)) return getCapabilityTypeAlign(ASTCtx).getQuantity(); return llvm::None; } @@ -301,20 +301,18 @@ bool isImplicitConversionFromVoidPtr(const Stmt *S, CheckerContext &C) { return !M.empty(); } -bool isGenericStorage(CheckerContext &C, const SVal &V) { - if (SymbolRef Sym = V.getAsSymbol()) { - if (!isGenericPointerType(Sym->getType(), false)) - return false; - if (const MemRegion *R = Sym->getOriginRegion()) { - const MemSpaceRegion *MS = R->getMemorySpace(); - if (isa(MS)) - return true; // global variable - if (auto SR = dyn_cast(MS)) - return SR->getStackFrame()->inTopFrame(); // top-level argument - - if (isa(R)) - return true; // struct field - } +bool isGenericStorage(SymbolRef Sym, QualType CopyTy) { + if (!isGenericPointerType(CopyTy, false)) + return false; + if (const MemRegion *R = Sym->getOriginRegion()) { + const MemSpaceRegion *MS = R->getMemorySpace(); + if (isa(MS)) + return true; // global variable + if (const auto *SR = dyn_cast(MS)) + return SR->getStackFrame()->inTopFrame(); // top-level argument + + if (isa(R)) + return true; // struct field } return false; } @@ -322,7 +320,10 @@ bool isGenericStorage(CheckerContext &C, const SVal &V) { bool isGenericStorage(CheckerContext &C, const Expr *E) { if (!isGenericPointerType(E->IgnoreImpCasts()->getType(), false)) return false; - return isGenericStorage(C, C.getSVal(E)); + if (SymbolRef Sym = C.getSVal(E).getAsSymbol()) { + return isGenericStorage(Sym, Sym->getType()); + } + return false; } static void describeObjectType(raw_ostream &OS, const QualType &Ty, @@ -403,17 +404,18 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, const QualType &DstTy = BO->getLHS()->getType(); if (!DstTy->isCHERICapabilityType(ASTCtx, true)) return; - - bool DstIsCapStorage = false, DstIsGenericStorage = false; + + /* Check if dst pointee type contains capabilities or is a generic storage type (can contain arbitrary data) */ + bool DstIsPtr2CapStorage = false, DstIsPtr2GenStorage = false; if (DstTy->isPointerType() && hasCapability(DstTy->getPointeeType(), ASTCtx)) - DstIsCapStorage = true; + DstIsPtr2CapStorage = true; if (const MemRegion *MR = L.getAsRegion()) { if (const TypedValueRegion *TR = MR->getAs()) { const QualType &Ty = TR->getValueType(); - DstIsCapStorage |= Ty->isPointerType() && hasCapability(Ty->getPointeeType(), ASTCtx); - DstIsGenericStorage |= isGenericPointerType(Ty, false) && + DstIsPtr2CapStorage |= Ty->isPointerType() && hasCapability(Ty->getPointeeType(), ASTCtx); + DstIsPtr2GenStorage |= isGenericPointerType(Ty, false) && (isa(TR->getMemorySpace()) || isa(TR->StripCasts())); } } @@ -422,34 +424,41 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, const QualType &SymTy = Sym->getType(); if (SymTy->isPointerType()) { const QualType &CopyTy = SymTy->getPointeeType(); - DstIsCapStorage |= CopyTy->isPointerType() && hasCapability(CopyTy->getPointeeType(), ASTCtx); - if (const MemRegion *R = Sym->getOriginRegion()) { - const MemSpaceRegion *MS = R->getMemorySpace(); - bool TopLevelArg = false; - if (auto SS = dyn_cast(MS)) - TopLevelArg = SS->getStackFrame()->inTopFrame(); - DstIsGenericStorage |= isGenericPointerType(CopyTy, false) && - (TopLevelArg || isa(MS) || isa(R->StripCasts())); - } + DstIsPtr2CapStorage |= CopyTy->isPointerType() && hasCapability(CopyTy->getPointeeType(), ASTCtx); + DstIsPtr2GenStorage |= isGenericStorage(Sym, CopyTy); } } - if (!DstIsCapStorage && !DstIsGenericStorage) + if (!DstIsPtr2CapStorage && !DstIsPtr2GenStorage) return; - /* Calculate actual alignment */ + /* Source alignment */ unsigned CapAlign = getCapabilityTypeAlign(ASTCtx).getQuantity(); const Optional &SrcAlign = getActualAlignment(C, V); if (!SrcAlign.hasValue() || SrcAlign >= CapAlign) return; + if (DstIsPtr2GenStorage) { + /* Skip if src pointee value is known and contains no capabilities */ + if (const MemRegion *SrcMR = V.getAsRegion()) { + if (const TypedValueRegion *SrcTR = SrcMR->StripCasts()->getAs()) { + const QualType &SrcValTy = SrcTR->getValueType(); + const SVal &SrcDeref = C.getState()->getSVal(SrcMR, SrcValTy); + SymbolRef DerefSym = SrcDeref.getAsSymbol(); + // Emit if SrcDeref is undef/unknown or represents initial value of this region + if (!DerefSym || DerefSym->getOriginRegion()->StripCasts() != SrcTR) + return; + } + } + } + /* Emit warning */ SmallString<350> ErrorMessage; llvm::raw_svector_ostream OS(ErrorMessage); OS << "Pointer value aligned to a " << SrcAlign << " byte boundary"; OS << " stored as type '" << BO->getLHS()->getType().getAsString() << "'"; OS << ". Memory pointed by it"; - if (DstIsCapStorage) + if (DstIsPtr2CapStorage) OS << " is supposed to"; else OS << " may be used to"; diff --git a/clang/test/Analysis/pointer-alignment.c b/clang/test/Analysis/pointer-alignment.c index 03b21dbd33d5..27bd4788786a 100644 --- a/clang/test/Analysis/pointer-alignment.c +++ b/clang/test/Analysis/pointer-alignment.c @@ -152,7 +152,9 @@ void alloc(void** p) { //expected-warning@-1{{Pointer value aligned to a 1 byte boundary stored as type 'void * __capability'. Memory pointed by it is supposed to hold capabilities, for which 16-byte capability alignment will be required}} } -void test(void) { +void test_assign(struct T *pT) { intptr_t *cp; // expected-note{{Capabilities stored}} alloc((void**)&cp); + + pT->p = (void*)"string"; // no warning } From 6192b5181c67c16e5bf26a05fb0eeb228aa3f2af Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 5 Feb 2024 16:25:22 +0000 Subject: [PATCH 47/77] [CHERI_CSA] PointerAlignmentChecker: improve warning notes --- .../Checkers/PointerAlignmentChecker.cpp | 63 ++++++++++++------- clang/test/Analysis/pointer-alignment.c | 2 +- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index 082b1df43aae..169337a5da92 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -73,7 +73,8 @@ class PointerAlignmentChecker ExplodedNode *emitAlignmentWarning(CheckerContext &C, const SVal &UnderalignedPtrVal, const BugType &BT, StringRef ErrorMessage, - const ValueDecl *CapSrcDecl = nullptr) const; + const ValueDecl *CapStorageDecl = nullptr, + StringRef CapSrcMsg = StringRef()) const; class AlignmentBugVisitor : public BugReporterVisitor { public: @@ -411,6 +412,7 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, if (DstTy->isPointerType() && hasCapability(DstTy->getPointeeType(), ASTCtx)) DstIsPtr2CapStorage = true; + const ValueDecl *CapDstDecl = nullptr; if (const MemRegion *MR = L.getAsRegion()) { if (const TypedValueRegion *TR = MR->getAs()) { const QualType &Ty = TR->getValueType(); @@ -418,6 +420,8 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, DstIsPtr2GenStorage |= isGenericPointerType(Ty, false) && (isa(TR->getMemorySpace()) || isa(TR->StripCasts())); } + if (const DeclRegion *D = getOriginalAllocation(MR)) + CapDstDecl = D->getDecl(); } if (SymbolRef Sym = L.getAsSymbol()) { @@ -465,13 +469,19 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, OS << " hold capabilities, for which " << CapAlign << "-byte capability alignment will be required"; - const ValueDecl *CapDstDecl = nullptr; - if (const MemRegion *SR = L.getAsRegion()) { - if (const DeclRegion *D = getOriginalAllocation(SR)) - CapDstDecl = D->getDecl(); - } - emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, CapDstDecl); + if (CapDstDecl) { + SmallString<350> Note; + llvm::raw_svector_ostream OS2(Note); + OS2 << "Memory pointed by"; + if (DstIsPtr2CapStorage) { + const QualType &AllocType = CapDstDecl->getType().getCanonicalType(); + OS2 << " '" << AllocType.getAsString() << "'" << " value is supposed to hold capabilities"; + } else + OS2 << " this value may be used to hold capabilities"; + emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, CapDstDecl, Note); + } else + emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage); } void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, @@ -527,7 +537,14 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, CapSrcDecl = D->getDecl(); } - emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, CapSrcDecl); + if (CapSrcDecl) { + SmallString<350> Note; + llvm::raw_svector_ostream OS2(Note); + const QualType &AllocType = CapSrcDecl->getType().getCanonicalType(); + OS2 << "Capabilities stored in " << "'" << AllocType.getAsString() << "'"; + emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, CapSrcDecl, Note); + } else + emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage); return; } @@ -548,13 +565,20 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, " stripped earlier due to underaligned storage."; - const ValueDecl *CapSrcDecl = nullptr; + const ValueDecl *CapDstDecl = nullptr; if (const MemRegion *SR = DstVal.getAsRegion()) { if (const DeclRegion *D = getOriginalAllocation(SR)) - CapSrcDecl = D->getDecl(); + CapDstDecl = D->getDecl(); } - emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, CapSrcDecl); + if (CapDstDecl) { + SmallString<350> Note; + llvm::raw_svector_ostream OS2(Note); + const QualType &AllocType = CapDstDecl->getType().getCanonicalType(); + OS2 << "Capabilities stored in " << "'" << AllocType.getAsString() << "'"; + emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, CapDstDecl, Note); + } else + emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage); return; } @@ -805,13 +829,8 @@ void describeOriginalAllocation(const ValueDecl *SrcDecl, void describeCapabilityStorage(const ValueDecl *CapDecl, PathDiagnosticLocation SrcLoc, PathSensitiveBugReport &W, - ASTContext &ASTCtx) { - SmallString<350> Note; - llvm::raw_svector_ostream OS2(Note); - const QualType &AllocType = CapDecl->getType().getCanonicalType(); - OS2 << "Capabilities stored in "; - OS2 << "'" << AllocType.getAsString() << "'"; - W.addNote(Note, SrcLoc); + StringRef CapStorageMsg) { + W.addNote(CapStorageMsg, SrcLoc); } } // namespace @@ -822,7 +841,7 @@ PointerAlignmentChecker::emitAlignmentWarning( const SVal &UnderalignedPtrVal, const BugType &BT, StringRef ErrorMessage, - const ValueDecl *CapSrcDecl) const { + const ValueDecl *CapStorageDecl, StringRef CapStorageMsg) const { ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); if (!ErrNode) return nullptr; @@ -847,10 +866,10 @@ PointerAlignmentChecker::emitAlignmentWarning( describeOriginalAllocation(MRDecl, MRDeclLoc, *W, C.getASTContext()); } - if (CapSrcDecl) { + if (CapStorageDecl) { auto CapSrcDeclLoc = - PathDiagnosticLocation::create(CapSrcDecl, C.getSourceManager()); - describeCapabilityStorage(CapSrcDecl, CapSrcDeclLoc, *W, C.getASTContext()); + PathDiagnosticLocation::create(CapStorageDecl, C.getSourceManager()); + describeCapabilityStorage(CapStorageDecl, CapSrcDeclLoc, *W, CapStorageMsg); } C.emitReport(std::move(W)); diff --git a/clang/test/Analysis/pointer-alignment.c b/clang/test/Analysis/pointer-alignment.c index 27bd4788786a..eb40ae5717ca 100644 --- a/clang/test/Analysis/pointer-alignment.c +++ b/clang/test/Analysis/pointer-alignment.c @@ -153,7 +153,7 @@ void alloc(void** p) { } void test_assign(struct T *pT) { - intptr_t *cp; // expected-note{{Capabilities stored}} + intptr_t *cp; // expected-note{{Memory pointed by '__intcap * __capability' value is supposed to hold capabilities}} alloc((void**)&cp); pT->p = (void*)"string"; // no warning From 76832fdd0e1df3fa0bf2dcc4797b1dd855a8670d Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 6 Feb 2024 14:35:28 +0000 Subject: [PATCH 48/77] [CHERI_CSA] PointerAlignmentChecker: suppress duplicate reports Report warning when an underaligned pointer gets converted or stored as a capability-aligned pointer value for the first time. Do not report if it already has a strictly-aligned type. --- .../Checkers/PointerAlignmentChecker.cpp | 23 +++++++++++++++---- clang/test/Analysis/pointer-alignment.c | 2 ++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index 169337a5da92..b1eab1886947 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -348,6 +348,11 @@ const DeclRegion *getOriginalAllocation(const MemRegion *MR) { return nullptr; } +bool hasCapStorageType(const Expr *E, ASTContext &ASTCtx) { + const QualType &Ty = E->IgnoreCasts()->getType(); + return Ty->isPointerType() && hasCapability(Ty->getPointeeType(), ASTCtx); +} + } // namespace void PointerAlignmentChecker::checkPreStmt(const CastExpr *CE, @@ -360,6 +365,11 @@ void PointerAlignmentChecker::checkPreStmt(const CastExpr *CE, ASTContext &ASTCtx = C.getASTContext(); + if (hasCapStorageType(CE->getSubExpr(), ASTCtx)) { + /* Src value must have been already checked for capability alignment by this time */ + return; + } + /* Calculate required alignment */ const Optional &DstReqAlign = getRequiredAlignment(ASTCtx, CE->getType(), false); @@ -406,6 +416,11 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, if (!DstTy->isCHERICapabilityType(ASTCtx, true)) return; + if (hasCapStorageType(BO->getRHS(), ASTCtx)) { + /* Src value must have been already checked for capability alignment by this time */ + return; + } + /* Check if dst pointee type contains capabilities or is a generic storage type (can contain arbitrary data) */ bool DstIsPtr2CapStorage = false, DstIsPtr2GenStorage = false; @@ -442,15 +457,16 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, if (!SrcAlign.hasValue() || SrcAlign >= CapAlign) return; - if (DstIsPtr2GenStorage) { - /* Skip if src pointee value is known and contains no capabilities */ + if (!DstIsPtr2CapStorage) { + /* Dst is generic pointer; + * Skip if src pointee value is known and contains no capabilities */ if (const MemRegion *SrcMR = V.getAsRegion()) { if (const TypedValueRegion *SrcTR = SrcMR->StripCasts()->getAs()) { const QualType &SrcValTy = SrcTR->getValueType(); const SVal &SrcDeref = C.getState()->getSVal(SrcMR, SrcValTy); SymbolRef DerefSym = SrcDeref.getAsSymbol(); // Emit if SrcDeref is undef/unknown or represents initial value of this region - if (!DerefSym || DerefSym->getOriginRegion()->StripCasts() != SrcTR) + if (!DerefSym || !DerefSym->getOriginRegion() || DerefSym->getOriginRegion()->StripCasts() != SrcTR) return; } } @@ -469,7 +485,6 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, OS << " hold capabilities, for which " << CapAlign << "-byte capability alignment will be required"; - if (CapDstDecl) { SmallString<350> Note; llvm::raw_svector_ostream OS2(Note); diff --git a/clang/test/Analysis/pointer-alignment.c b/clang/test/Analysis/pointer-alignment.c index eb40ae5717ca..2959ebcfce21 100644 --- a/clang/test/Analysis/pointer-alignment.c +++ b/clang/test/Analysis/pointer-alignment.c @@ -146,6 +146,7 @@ void gen_storage(struct T *pT, void *p, size_t n) { // ---- char extra[100]; // expected-note{{Original allocation}} +void *gP; void alloc(void** p) { *p = extra; @@ -155,6 +156,7 @@ void alloc(void** p) { void test_assign(struct T *pT) { intptr_t *cp; // expected-note{{Memory pointed by '__intcap * __capability' value is supposed to hold capabilities}} alloc((void**)&cp); + gP = cp; // no duplicate warning pT->p = (void*)"string"; // no warning } From 894c785def794e97d9d14410c620c13e21928970 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 9 Feb 2024 17:26:49 +0000 Subject: [PATCH 49/77] [CHERI_CSA] PointerAlignmentChecker: improve messages & traces --- .../Checkers/PointerAlignmentChecker.cpp | 152 ++++++++++++------ clang/test/Analysis/pointer-alignment.c | 21 +-- 2 files changed, 111 insertions(+), 62 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index b1eab1886947..9564c6015dd8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -73,6 +73,7 @@ class PointerAlignmentChecker ExplodedNode *emitAlignmentWarning(CheckerContext &C, const SVal &UnderalignedPtrVal, const BugType &BT, StringRef ErrorMessage, + const MemRegion *CapStorageReg = nullptr, const ValueDecl *CapStorageDecl = nullptr, StringRef CapSrcMsg = StringRef()) const; @@ -92,6 +93,24 @@ class PointerAlignmentChecker private: SymbolRef Sym; }; + + class CapStorageBugVisitor : public BugReporterVisitor { + public: + CapStorageBugVisitor(const MemRegion *MR) : MemReg(MR) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(MemReg); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; + private: + const MemRegion *MemReg; + }; + }; } // namespace @@ -423,19 +442,24 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, /* Check if dst pointee type contains capabilities or is a generic storage type (can contain arbitrary data) */ bool DstIsPtr2CapStorage = false, DstIsPtr2GenStorage = false; + QualType DstCapStorageTy = DstTy; if (DstTy->isPointerType() && hasCapability(DstTy->getPointeeType(), ASTCtx)) DstIsPtr2CapStorage = true; const ValueDecl *CapDstDecl = nullptr; - if (const MemRegion *MR = L.getAsRegion()) { - if (const TypedValueRegion *TR = MR->getAs()) { + const MemRegion *CapStorageReg = L.getAsRegion(); + if (CapStorageReg) { + if (const TypedValueRegion *TR = CapStorageReg->getAs()) { const QualType &Ty = TR->getValueType(); - DstIsPtr2CapStorage |= Ty->isPointerType() && hasCapability(Ty->getPointeeType(), ASTCtx); + if (Ty->isPointerType() && hasCapability(Ty->getPointeeType(), ASTCtx)) { + DstIsPtr2CapStorage = true; + DstCapStorageTy = Ty; + } DstIsPtr2GenStorage |= isGenericPointerType(Ty, false) && (isa(TR->getMemorySpace()) || isa(TR->StripCasts())); } - if (const DeclRegion *D = getOriginalAllocation(MR)) + if (const DeclRegion *D = getOriginalAllocation(CapStorageReg)) CapDstDecl = D->getDecl(); } @@ -443,7 +467,10 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, const QualType &SymTy = Sym->getType(); if (SymTy->isPointerType()) { const QualType &CopyTy = SymTy->getPointeeType(); - DstIsPtr2CapStorage |= CopyTy->isPointerType() && hasCapability(CopyTy->getPointeeType(), ASTCtx); + if (!DstIsPtr2CapStorage && CopyTy->isPointerType() && hasCapability(CopyTy->getPointeeType(), ASTCtx)) { + DstIsPtr2CapStorage = true; + DstCapStorageTy = CopyTy; + } DstIsPtr2GenStorage |= isGenericStorage(Sym, CopyTy); } } @@ -476,14 +503,13 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, SmallString<350> ErrorMessage; llvm::raw_svector_ostream OS(ErrorMessage); OS << "Pointer value aligned to a " << SrcAlign << " byte boundary"; - OS << " stored as type '" << BO->getLHS()->getType().getAsString() << "'"; - OS << ". Memory pointed by it"; + OS << " stored as value of type '" << DstCapStorageTy.getAsString() << "'."; + OS << " Memory pointed by it"; if (DstIsPtr2CapStorage) OS << " is supposed to"; else OS << " may be used to"; - OS << " hold capabilities, for which " << CapAlign - << "-byte capability alignment will be required"; + OS << " hold capabilities, for which " << CapAlign << "-byte capability alignment will be required"; if (CapDstDecl) { SmallString<350> Note; @@ -494,9 +520,9 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, OS2 << " '" << AllocType.getAsString() << "'" << " value is supposed to hold capabilities"; } else OS2 << " this value may be used to hold capabilities"; - emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, CapDstDecl, Note); + emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, CapStorageReg, CapDstDecl, Note); } else - emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage); + emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, CapStorageReg); } void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, @@ -546,9 +572,10 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, << " Storing a capability at an underaligned address" " leads to tag stripping."; + const MemRegion *CapStorageReg = SrcVal.getAsRegion(); const ValueDecl *CapSrcDecl = nullptr; - if (const MemRegion *SR = SrcVal.getAsRegion()) { - if (const DeclRegion *D = getOriginalAllocation(SR)) + if (CapStorageReg) { + if (const DeclRegion *D = getOriginalAllocation(CapStorageReg)) CapSrcDecl = D->getDecl(); } @@ -557,9 +584,9 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, llvm::raw_svector_ostream OS2(Note); const QualType &AllocType = CapSrcDecl->getType().getCanonicalType(); OS2 << "Capabilities stored in " << "'" << AllocType.getAsString() << "'"; - emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, CapSrcDecl, Note); + emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, CapStorageReg, CapSrcDecl, Note); } else - emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage); + emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, CapStorageReg); return; } @@ -570,19 +597,19 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, OS << "Destination memory object"; describeObjectType(OS, DstTy, ASTCtx.getLangOpts()); if (!DstIsCapStorage) - OS << " may"; + OS << " may be"; else - OS << " is supposed to"; - OS << " contain capabilities that require " + OS << " is"; + OS << " supposed to contain capabilities that require " << CapAlign << "-byte capability alignment."; OS << " Source address alignment is " << SrcCurAlign << ", which means" " that copied object may have its capabilities tags" " stripped earlier due to underaligned storage."; - + const MemRegion *CapStorageReg = DstVal.getAsRegion(); const ValueDecl *CapDstDecl = nullptr; - if (const MemRegion *SR = DstVal.getAsRegion()) { - if (const DeclRegion *D = getOriginalAllocation(SR)) + if (CapStorageReg) { + if (const DeclRegion *D = getOriginalAllocation(CapStorageReg)) CapDstDecl = D->getDecl(); } @@ -591,30 +618,22 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, llvm::raw_svector_ostream OS2(Note); const QualType &AllocType = CapDstDecl->getType().getCanonicalType(); OS2 << "Capabilities stored in " << "'" << AllocType.getAsString() << "'"; - emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, CapDstDecl, Note); + emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, CapStorageReg, CapDstDecl, Note); } else - emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage); + emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, CapStorageReg); return; } /* Propagate CapStorage flag */ if (SrcIsCapStorage && !DstIsCapStorage) { if (const MemRegion *R = DstVal.getAsRegion()) { - const ProgramStateRef State = - C.getState()->add(R->StripCasts()); - const NoteTag *Tag = - C.getNoteTag("Copied memory object contains capabilities"); - C.addTransition(State, C.getPredecessor(), Tag); + C.addTransition(C.getState()->add(R->StripCasts())); } } if (!SrcIsCapStorage && DstIsCapStorage) { if (const MemRegion *R = SrcVal.getAsRegion()) { - const ProgramStateRef State = - C.getState()->add(R->StripCasts()); - const NoteTag *Tag = - C.getNoteTag("Copied memory object should contain capabilities"); - C.addTransition(State, C.getPredecessor(), Tag); + C.addTransition(C.getState()->add(R->StripCasts())); } } } @@ -678,13 +697,10 @@ void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, /* Update CapStorageSet */ const ProgramPointTag *Tag = nullptr; if (!isCapabilityStorage(DstVal, State, ASTCtx)) { - if ((isCapabilityStorage(SrcVal, State, ASTCtx) && !isNonZeroShift(SrcVal)) - || DstIsCapStorage) { + if ((isCapabilityStorage(SrcVal, State, ASTCtx) && !isNonZeroShift(SrcVal)) || DstIsCapStorage) { if (const MemRegion *R = DstVal.getAsRegion()) { - State = State->add(R->StripCasts()); - Updated = true; - if (DstIsCapStorage) - Tag = C.getNoteTag("Cast to capability-containing type"); + State = State->add(R->StripCasts()); + Updated = true; } } } @@ -835,19 +851,15 @@ void describeOriginalAllocation(const ValueDecl *SrcDecl, const QualType &AllocType = SrcDecl->getType().getCanonicalType(); OS2 << "Original allocation of type "; OS2 << "'" << AllocType.getAsString() << "'"; - OS2 << " which has an alignment requirement "; - OS2 << ASTCtx.getTypeAlignInChars(AllocType).getQuantity(); - OS2 << " bytes"; + OS2 << " has an alignment requirement "; + unsigned Align = ASTCtx.getTypeAlignInChars(AllocType).getQuantity(); + if (Align == 1) + OS2 << "1 byte"; + else + OS2 << Align << " bytes"; W.addNote(Note, SrcLoc); } -void describeCapabilityStorage(const ValueDecl *CapDecl, - PathDiagnosticLocation SrcLoc, - PathSensitiveBugReport &W, - StringRef CapStorageMsg) { - W.addNote(CapStorageMsg, SrcLoc); -} - } // namespace ExplodedNode * @@ -856,6 +868,7 @@ PointerAlignmentChecker::emitAlignmentWarning( const SVal &UnderalignedPtrVal, const BugType &BT, StringRef ErrorMessage, + const MemRegion *CapStorageReg, const ValueDecl *CapStorageDecl, StringRef CapStorageMsg) const { ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); if (!ErrNode) @@ -882,9 +895,12 @@ PointerAlignmentChecker::emitAlignmentWarning( } if (CapStorageDecl) { - auto CapSrcDeclLoc = - PathDiagnosticLocation::create(CapStorageDecl, C.getSourceManager()); - describeCapabilityStorage(CapStorageDecl, CapSrcDeclLoc, *W, CapStorageMsg); + auto CapSrcDeclLoc = PathDiagnosticLocation::create(CapStorageDecl, C.getSourceManager()); + W->addNote(CapStorageMsg, CapSrcDeclLoc); + } + + if (CapStorageReg) { + W->addVisitor(std::make_unique(CapStorageReg)); } C.emitReport(std::move(W)); @@ -945,6 +961,38 @@ PathDiagnosticPieceRef PointerAlignmentChecker::AlignmentBugVisitor::VisitNode( return std::make_shared(Pos, OS.str(), true); } +PathDiagnosticPieceRef PointerAlignmentChecker::CapStorageBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { + + const Stmt *S = N->getStmtForDiagnostics(); + if (!S) + return nullptr; + + ProgramStateRef State = N->getState(); + ProgramStateRef Pred = N->getFirstPred()->getState(); + if (!State->contains(MemReg) || Pred->contains(MemReg)) + return nullptr; + + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + if (const CastExpr *CE = dyn_cast(S)) { + if (!CE->getType()->isPointerType() || isGenericPointerType(CE->getType(), true)) + return nullptr; + const QualType &DstPTy = CE->getType()->getPointeeType(); + if (!DstPTy->isIncompleteType() || !hasCapability(DstPTy, N->getCodeDecl().getASTContext())) + return nullptr; + OS << "Cast to capability-containing type"; + } else if (isa(S)) { + OS << "Copying memory object containing capabilities"; + } else + return nullptr; + + // Generate the extra diagnostic. + PathDiagnosticLocation const Pos(S, BRC.getSourceManager(), N->getLocationContext()); + return std::make_shared(Pos, OS.str(), true); +} + void ento::registerPointerAlignmentChecker(CheckerManager &mgr) { mgr.registerChecker(); } diff --git a/clang/test/Analysis/pointer-alignment.c b/clang/test/Analysis/pointer-alignment.c index 2959ebcfce21..6e2eaf519e0a 100644 --- a/clang/test/Analysis/pointer-alignment.c +++ b/clang/test/Analysis/pointer-alignment.c @@ -98,7 +98,7 @@ B* flex(size_t n) { char c_buf[100]; // expected-note{{Original allocation}} expected-note{{Original allocation}} expected-note{{Original allocation}} void implicit_cap_storage(void **impl_cap_ptr) { *impl_cap_ptr = &c_buf[0]; - // expected-warning@-1{{Pointer value aligned to a 1 byte boundary stored as type 'void * __capability'. Memory pointed by it may be used to hold capabilities, for which 16-byte capability alignment will be required}} + // expected-warning@-1{{Pointer value aligned to a 1 byte boundary stored as value of type 'void * __capability'. Memory pointed by it may be used to hold capabilities, for which 16-byte capability alignment will be required}} } char c_buf_aligned[100] __attribute__((aligned(_Alignof(void*)))); // expected-note{{Capabilities stored in 'char[100]'}} @@ -109,7 +109,7 @@ void copy_through_unaligned(intptr_t *src, void *dst, size_t n) { memcpy(c_buf, c_buf_aligned, n * sizeof(intptr_t)); // expected-warning@-1{{Copied memory object of type 'char[100]' contains capabilities that require 16-byte capability alignment. Destination address alignment is 1. Storing a capability at an underaligned address leads to tag stripping}} memcpy(d, c_buf, n * sizeof(intptr_t)); - // expected-warning@-1{{Destination memory object pointed by 'void * __capability' pointer may contain capabilities that require 16-byte capability alignment. Source address alignment is 1, which means that copied object may have its capabilities tags stripped earlier due to underaligned storage}} + // expected-warning@-1{{Destination memory object pointed by 'void * __capability' pointer may be supposed to contain capabilities that require 16-byte capability alignment. Source address alignment is 1, which means that copied object may have its capabilities tags stripped earlier due to underaligned storage}} } void after_cap_data(int n, int D) { @@ -122,8 +122,9 @@ void after_cap_data(int n, int D) { char a1[100], a2[100], a3[100], a4[100]; // expected-note{{Original allocation}} expected-note{{}} expected-note{{}} struct T { - void *p; -} gS; + void *pVoid; + intptr_t *pCap; +} *gS; void copy(void *dst, void* src, size_t n) { memcpy(dst, src, n); // no-warn @@ -132,11 +133,11 @@ void copy(void *dst, void* src, size_t n) { void gen_storage(struct T *pT, void *p, size_t n) { memcpy(a1, p, n); //expected-warning@-1{{Copied memory object pointed by 'void * __capability' pointer may contain capabilities that require 16-byte capability alignment. Destination address alignment is 1. Storing a capability at an underaligned address leads to tag stripping}} - memcpy(pT->p, a2, n); - //expected-warning@-1{{Destination memory object pointed by 'void * __capability' pointer may contain capabilities that require 16-byte capability alignment. Source address alignment is 1, which means that copied object may have its capabilities tags stripped earlier due to underaligned storage}} + memcpy(pT->pVoid, a2, n); + //expected-warning@-1{{Destination memory object pointed by 'void * __capability' pointer may be supposed to contain capabilities that require 16-byte capability alignment. Source address alignment is 1, which means that copied object may have its capabilities tags stripped earlier due to underaligned storage}} struct T *mT = malloc(sizeof(struct T)); - memcpy(a3, mT->p, n); + memcpy(a3, mT->pVoid, n); //expected-warning@-1{{Copied memory object pointed by 'void * __capability' pointer may contain capabilities that require 16-byte capability alignment. Destination address alignment is 1. Storing a capability at an underaligned address leads to tag stripping}} void *m = malloc(n); @@ -145,12 +146,12 @@ void gen_storage(struct T *pT, void *p, size_t n) { } // ---- -char extra[100]; // expected-note{{Original allocation}} +char extra[100]; // expected-note{{Original allocation of type 'char[100]' has an alignment requirement 1 byte}} void *gP; void alloc(void** p) { *p = extra; - //expected-warning@-1{{Pointer value aligned to a 1 byte boundary stored as type 'void * __capability'. Memory pointed by it is supposed to hold capabilities, for which 16-byte capability alignment will be required}} + //expected-warning@-1{{Pointer value aligned to a 1 byte boundary stored as value of type 'intptr_t * __capability'. Memory pointed by it is supposed to hold capabilities, for which 16-byte capability alignment will be required}} } void test_assign(struct T *pT) { @@ -158,5 +159,5 @@ void test_assign(struct T *pT) { alloc((void**)&cp); gP = cp; // no duplicate warning - pT->p = (void*)"string"; // no warning + pT->pVoid = (void*)"string"; // no warning } From 92ed22594d847128d1f555ca36f19f49ac0598d7 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 9 Feb 2024 18:33:08 +0000 Subject: [PATCH 50/77] [CHERI_CSA] PointerAlignmentChecker: rework handling symbolic addresses Rework detection of addresses of capability-containing regions and generic regions (those that are intended to hold arbitrary data) by analyzing address Symbol type and origin. --- .../Checkers/PointerAlignmentChecker.cpp | 27 ++++++++++++------- clang/test/Analysis/pointer-alignment.c | 12 ++++++--- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index 9564c6015dd8..3c5b06ec3755 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -135,12 +135,20 @@ PointerAlignmentChecker::PointerAlignmentChecker() { namespace { +bool isGlobalOrTopLevelArg(const MemSpaceRegion *MS) { + if (isa(MS)) + return true; // global variable + if (const auto *SR = dyn_cast(MS)) + return SR->getStackFrame()->inTopFrame(); // top-level argument + return false; +} + Optional globalOrParamPointeeType(SymbolRef Sym) { const MemRegion *BaseRegOrigin = Sym->getOriginRegion(); if (!BaseRegOrigin) return llvm::None; - if (!BaseRegOrigin->getMemorySpace()->hasGlobalsOrParametersStorage()) + if (!isGlobalOrTopLevelArg(BaseRegOrigin->getMemorySpace())) return llvm::None; const QualType &SymTy = Sym->getType(); @@ -248,9 +256,13 @@ int getTrailingZerosCount(const Expr *E, CheckerContext &C) { } bool isCapabilityStorage(SymbolRef Sym, ASTContext &ASTCtx) { - const Optional GlobalPointeeTy = globalOrParamPointeeType(Sym); - if (GlobalPointeeTy.hasValue()) - return hasCapability(GlobalPointeeTy.getValue(), ASTCtx); + const QualType &SymTy = Sym->getType(); + if (SymTy->isPointerType()) { + const QualType &PT = SymTy->getPointeeType(); + if (!PT->isIncompleteType()) { + return hasCapability(PT, ASTCtx); + } + } return false; } @@ -325,11 +337,8 @@ bool isGenericStorage(SymbolRef Sym, QualType CopyTy) { if (!isGenericPointerType(CopyTy, false)) return false; if (const MemRegion *R = Sym->getOriginRegion()) { - const MemSpaceRegion *MS = R->getMemorySpace(); - if (isa(MS)) - return true; // global variable - if (const auto *SR = dyn_cast(MS)) - return SR->getStackFrame()->inTopFrame(); // top-level argument + if (isGlobalOrTopLevelArg(R->getMemorySpace())) + return true; // global variable or top-level function argument if (isa(R)) return true; // struct field diff --git a/clang/test/Analysis/pointer-alignment.c b/clang/test/Analysis/pointer-alignment.c index 6e2eaf519e0a..86c824e63f67 100644 --- a/clang/test/Analysis/pointer-alignment.c +++ b/clang/test/Analysis/pointer-alignment.c @@ -119,7 +119,7 @@ void after_cap_data(int n, int D) { } // ---- -char a1[100], a2[100], a3[100], a4[100]; // expected-note{{Original allocation}} expected-note{{}} expected-note{{}} +char a1[100], a2[100], a3[100], a4[100], a5[100], a6[100]; // expected-note{{Original allocation}} expected-note{{}} expected-note{{}} expected-note{{}} expected-note{{}} struct T { void *pVoid; @@ -140,9 +140,15 @@ void gen_storage(struct T *pT, void *p, size_t n) { memcpy(a3, mT->pVoid, n); //expected-warning@-1{{Copied memory object pointed by 'void * __capability' pointer may contain capabilities that require 16-byte capability alignment. Destination address alignment is 1. Storing a capability at an underaligned address leads to tag stripping}} + memcpy(mT->pCap, a4, n); + //expected-warning@-1{{Destination memory object pointed by 'intptr_t * __capability' pointer is supposed to contain capabilities that require 16-byte capability alignment. Source address alignment is 1, which means that copied object may have its capabilities tags stripped earlier due to underaligned storage}} + + memcpy(gS->pCap, a5, n); + //expected-warning@-1{{Destination memory object pointed by 'intptr_t * __capability' pointer is supposed to contain capabilities that require 16-byte capability alignment. Source address alignment is 1, which means that copied object may have its capabilities tags stripped earlier due to underaligned storage}} + void *m = malloc(n); - memcpy(a4, m, n); // no-warn - copy(a4, m, n); + memcpy(a6, m, n); // no-warn + copy(a6, m, n); } // ---- From 08e449f57745dd3b2806ab04a1dcf16dab04add9 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 23 Feb 2024 17:12:27 +0000 Subject: [PATCH 51/77] [CHERI_CSA] PointerAlignmentChecker: false warnings suppression --- .../Checkers/PointerAlignmentChecker.cpp | 227 +++++++++++++----- clang/test/Analysis/pointer-alignment.c | 26 ++ 2 files changed, 193 insertions(+), 60 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index 3c5b06ec3755..91ac792f11eb 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -40,6 +40,7 @@ using namespace clang; using namespace ento; using namespace cheri; +using llvm::APSInt; namespace { class PointerAlignmentChecker @@ -70,12 +71,12 @@ class PointerAlignmentChecker private: - ExplodedNode *emitAlignmentWarning(CheckerContext &C, const SVal &UnderalignedPtrVal, - const BugType &BT, - StringRef ErrorMessage, - const MemRegion *CapStorageReg = nullptr, - const ValueDecl *CapStorageDecl = nullptr, - StringRef CapSrcMsg = StringRef()) const; + ExplodedNode *emitAlignmentWarning(CheckerContext &C, + const SVal &UnderalignedPtrVal, + const BugType &BT, StringRef ErrorMessage, + const MemRegion *CapStorageReg = nullptr, + const ValueDecl *CapStorageDecl = nullptr, + StringRef CapSrcMsg = StringRef()) const; class AlignmentBugVisitor : public BugReporterVisitor { public: @@ -175,8 +176,10 @@ int getTrailingZerosCount(SymbolRef Sym, ProgramStateRef State, Optional GlobalPointeeTy = globalOrParamPointeeType(Sym); if (GlobalPointeeTy.hasValue()) { QualType &PT = GlobalPointeeTy.getValue(); + if (PT->isCharType()) + return -1; // char* can be used as generic pointer unsigned A = ASTCtx.getTypeAlignInChars(PT).getQuantity(); - return llvm::APSInt::getUnsigned(A).countTrailingZeros(); + return APSInt::getUnsigned(A).countTrailingZeros(); } return -1; @@ -196,14 +199,35 @@ int getTrailingZerosCount(const MemRegion *R, ProgramStateRef State, unsigned NaturalAlign = ASTCtx.getTypeAlignInChars(PT).getQuantity(); if (const ElementRegion *ER = R->getAs()) { - int ElTyTZ = llvm::APSInt::getUnsigned(NaturalAlign).countTrailingZeros(); + int ElTyTZ = APSInt::getUnsigned(NaturalAlign).countTrailingZeros(); const MemRegion *Base = ER->getSuperRegion(); int BaseTZC = getTrailingZerosCount(Base, State, ASTCtx); if (BaseTZC < 0) return ElTyTZ > 0 ? ElTyTZ : -1; - int IdxTZC = getTrailingZerosCount(ER->getIndex(), State, ASTCtx); + NonLoc Idx = ER->getIndex(); + auto ConstIdx = Idx.getAs(); + if (ConstIdx.hasValue()) { + const APSInt &CIdx = ConstIdx.getValue().getValue(); + if (CIdx.isNegative()) { + // offsetof ? + if (const FieldRegion *BaseField = dyn_cast(Base)) { + const RegionOffset &Offset = BaseField->getAsOffset(); + if (!Offset.hasSymbolicOffset()) { + uint64_t FieldOffsetBits =Offset.getOffset(); + const CharUnits &FO = ASTCtx.toCharUnitsFromBits(FieldOffsetBits); + if (CIdx.getExtValue() == -FO.getQuantity()) { + const MemRegion *Parent = BaseField->getSuperRegion(); + return getTrailingZerosCount(Parent, State, ASTCtx); + } + } + } + return -1; + } + } + + int IdxTZC = getTrailingZerosCount(Idx, State, ASTCtx); if (IdxTZC < 0 && NaturalAlign == 1) return -1; @@ -219,7 +243,7 @@ int getTrailingZerosCount(const MemRegion *R, ProgramStateRef State, } unsigned A = std::max(NaturalAlign, AlignAttrVal); - return llvm::APSInt::getUnsigned(A).countTrailingZeros(); + return APSInt::getUnsigned(A).countTrailingZeros(); } return -1; } @@ -376,6 +400,20 @@ const DeclRegion *getOriginalAllocation(const MemRegion *MR) { return nullptr; } +unsigned int getFragileAlignment(const MemRegion *MR, + const ProgramStateRef State, + ASTContext& ASTCtx) { + const RegionOffset &RO = MR->getAsOffset(); + if (RO.hasSymbolicOffset()) + return 0; + int BaseAlign = getTrailingZerosCount(RO.getRegion(), State, ASTCtx); + if (BaseAlign < 0) + return 0; + assert(BaseAlign < 64); + unsigned OffsetAlign = APSInt::get(RO.getOffset()).countTrailingZeros(); + return 1L << std::min((unsigned)BaseAlign, OffsetAlign); +} + bool hasCapStorageType(const Expr *E, ASTContext &ASTCtx) { const QualType &Ty = E->IgnoreCasts()->getType(); return Ty->isPointerType() && hasCapability(Ty->getPointeeType(), ASTCtx); @@ -394,7 +432,8 @@ void PointerAlignmentChecker::checkPreStmt(const CastExpr *CE, ASTContext &ASTCtx = C.getASTContext(); if (hasCapStorageType(CE->getSubExpr(), ASTCtx)) { - /* Src value must have been already checked for capability alignment by this time */ + /* Src value must have been already checked + * for capability alignment by this time */ return; } @@ -445,11 +484,13 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, return; if (hasCapStorageType(BO->getRHS(), ASTCtx)) { - /* Src value must have been already checked for capability alignment by this time */ + /* Src value must have been already checked + * for capability alignment by this time */ return; } - /* Check if dst pointee type contains capabilities or is a generic storage type (can contain arbitrary data) */ + /* Check if dst pointee type contains capabilities or + * is a generic storage type (can contain arbitrary data) */ bool DstIsPtr2CapStorage = false, DstIsPtr2GenStorage = false; QualType DstCapStorageTy = DstTy; @@ -466,7 +507,8 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, DstCapStorageTy = Ty; } DstIsPtr2GenStorage |= isGenericPointerType(Ty, false) && - (isa(TR->getMemorySpace()) || isa(TR->StripCasts())); + (isa(TR->getMemorySpace()) || + isa(TR->StripCasts())); } if (const DeclRegion *D = getOriginalAllocation(CapStorageReg)) CapDstDecl = D->getDecl(); @@ -476,8 +518,9 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, const QualType &SymTy = Sym->getType(); if (SymTy->isPointerType()) { const QualType &CopyTy = SymTy->getPointeeType(); - if (!DstIsPtr2CapStorage && CopyTy->isPointerType() && hasCapability(CopyTy->getPointeeType(), ASTCtx)) { - DstIsPtr2CapStorage = true; + if (!DstIsPtr2CapStorage && CopyTy->isPointerType() && + hasCapability(CopyTy->getPointeeType(), ASTCtx)) { + DstIsPtr2CapStorage = true; DstCapStorageTy = CopyTy; } DstIsPtr2GenStorage |= isGenericStorage(Sym, CopyTy); @@ -497,12 +540,15 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, /* Dst is generic pointer; * Skip if src pointee value is known and contains no capabilities */ if (const MemRegion *SrcMR = V.getAsRegion()) { - if (const TypedValueRegion *SrcTR = SrcMR->StripCasts()->getAs()) { + if (const TypedValueRegion *SrcTR = + SrcMR->StripCasts()->getAs()) { const QualType &SrcValTy = SrcTR->getValueType(); const SVal &SrcDeref = C.getState()->getSVal(SrcMR, SrcValTy); SymbolRef DerefSym = SrcDeref.getAsSymbol(); - // Emit if SrcDeref is undef/unknown or represents initial value of this region - if (!DerefSym || !DerefSym->getOriginRegion() || DerefSym->getOriginRegion()->StripCasts() != SrcTR) + // Emit if SrcDeref is undef/unknown or represents + // the initial value of this region + if (!DerefSym || !DerefSym->getOriginRegion() || + DerefSym->getOriginRegion()->StripCasts() != SrcTR) return; } } @@ -518,7 +564,8 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, OS << " is supposed to"; else OS << " may be used to"; - OS << " hold capabilities, for which " << CapAlign << "-byte capability alignment will be required"; + OS << " hold capabilities, for which " << CapAlign + << "-byte capability alignment will be required"; if (CapDstDecl) { SmallString<350> Note; @@ -526,12 +573,15 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, OS2 << "Memory pointed by"; if (DstIsPtr2CapStorage) { const QualType &AllocType = CapDstDecl->getType().getCanonicalType(); - OS2 << " '" << AllocType.getAsString() << "'" << " value is supposed to hold capabilities"; + OS2 << " '" << AllocType.getAsString() << "'" + << " value is supposed to hold capabilities"; } else OS2 << " this value may be used to hold capabilities"; - emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, CapStorageReg, CapDstDecl, Note); + emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, + CapStorageReg, CapDstDecl, Note); } else - emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, CapStorageReg); + emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, + CapStorageReg); } void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, @@ -547,6 +597,21 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, if (!MemCpyParamPair) return; + /* Check size if big enough to copy a capability */ + SValBuilder &SVB = C.getSValBuilder(); + unsigned CapSize = getCapabilityTypeSize(ASTCtx).getQuantity(); + const NonLoc &CapSizeV = SVB.makeIntVal(CapSize, true); + const SVal &CopySizeV = C.getSVal(Call.getArgExpr(2)); + auto CSN = CopySizeV.getAs(); + if (CSN.hasValue()) { + auto LongCopy = + SVB.evalBinOpNN(C.getState(), clang::BO_GE, CSN.getValue(), CapSizeV, + SVB.getConditionType()); + if (auto LC = LongCopy.getAs()) + if (!C.getState()->assume(*LC, true)) + return; + } + unsigned CapAlign = getCapabilityTypeAlign(ASTCtx).getQuantity(); /* Destination alignment */ @@ -593,12 +658,14 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, llvm::raw_svector_ostream OS2(Note); const QualType &AllocType = CapSrcDecl->getType().getCanonicalType(); OS2 << "Capabilities stored in " << "'" << AllocType.getAsString() << "'"; - emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, CapStorageReg, CapSrcDecl, Note); + emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, + CapStorageReg, CapSrcDecl, Note); } else - emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, CapStorageReg); + emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, + CapStorageReg); return; } - + if ((DstIsCapStorage || DstIsGenStorage) && SrcCurAlign.hasValue() && SrcCurAlign < CapAlign) { SmallString<350> ErrorMessage; @@ -627,9 +694,11 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, llvm::raw_svector_ostream OS2(Note); const QualType &AllocType = CapDstDecl->getType().getCanonicalType(); OS2 << "Capabilities stored in " << "'" << AllocType.getAsString() << "'"; - emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, CapStorageReg, CapDstDecl, Note); + emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, + CapStorageReg, CapDstDecl, Note); } else - emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, CapStorageReg); + emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, + CapStorageReg); return; } @@ -657,6 +726,42 @@ bool isNonZeroShift(const SVal& V) { return false; } +bool valueIsLTPow2(const Expr *E, unsigned P, CheckerContext &C) { + if (P >= sizeof(uint64_t) * 8) + return true; + SValBuilder &SVB = C.getSValBuilder(); + const NonLoc &B = SVB.makeIntVal((uint64_t)1 << P, true); + + ProgramStateRef State = C.getState(); + const SVal &V = C.getSVal(E); + auto LT = SVB.evalBinOp(State, BO_LT, V, B, E->getType()); + return !State->assume(LT.castAs(), false); +} + +unsigned getBaseAlignFromOffsetOf(const Expr* E, ASTContext &Ctx) { + if (const CastExpr *CE = dyn_cast(E)) + return getBaseAlignFromOffsetOf(CE->IgnoreCasts(), Ctx); + + const OffsetOfExpr *OOE = dyn_cast(E); + if (!OOE) + return 0; + + unsigned res = 0; + const OffsetOfNode &BaseNode = OOE->getComponent(0); + switch (BaseNode.getKind()) { + case clang::OffsetOfNode::Field: { + RecordDecl *BaseRec = BaseNode.getField()->getParent(); + const QualType &BaseType = Ctx.getRecordType(BaseRec); + res = Ctx.getTypeAlignInChars(BaseType).getQuantity(); + break; + } + default: + break; + } + + return res; +} + } // namespace void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, @@ -670,14 +775,11 @@ void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, int SrcTZC = getTrailingZerosCount(CE->getSubExpr(), C); ASTContext &ASTCtx = C.getASTContext(); - int DstReqTZC = -1; bool DstIsCapStorage = false; if (CE->getType()->isPointerType()) { if (!isGenericPointerType(CE->getType(), true)) { const QualType &DstPTy = CE->getType()->getPointeeType(); if (!DstPTy->isIncompleteType()) { - unsigned ReqAl = ASTCtx.getTypeAlignInChars(DstPTy).getQuantity(); - DstReqTZC = llvm::APSInt::getUnsigned(ReqAl).countTrailingZeros(); DstIsCapStorage = hasCapability(DstPTy, ASTCtx); } } @@ -689,8 +791,7 @@ void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, bool Updated = false; /* Update TrailingZerosMap */ - int NewAlign = std::max(SrcTZC, DstReqTZC); - if (DstTZC < NewAlign) { + if (DstTZC < SrcTZC) { if (DstVal.isUnknown()) { const LocationContext *LCtx = C.getLocationContext(); DstVal = C.getSValBuilder().conjureSymbolVal( @@ -698,7 +799,7 @@ void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, State = State->BindExpr(CE, LCtx, DstVal); } if (SymbolRef Sym = DstVal.getAsSymbol()) { - State = State->set(Sym, NewAlign); + State = State->set(Sym, SrcTZC); Updated = true; } } @@ -706,7 +807,8 @@ void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, /* Update CapStorageSet */ const ProgramPointTag *Tag = nullptr; if (!isCapabilityStorage(DstVal, State, ASTCtx)) { - if ((isCapabilityStorage(SrcVal, State, ASTCtx) && !isNonZeroShift(SrcVal)) || DstIsCapStorage) { + if ((isCapabilityStorage(SrcVal, State, ASTCtx) && !isNonZeroShift(SrcVal)) + || DstIsCapStorage) { if (const MemRegion *R = DstVal.getAsRegion()) { State = State->add(R->StripCasts()); Updated = true; @@ -718,18 +820,6 @@ void PointerAlignmentChecker::checkPostStmt(const CastExpr *CE, C.addTransition(State, Tag); } -bool valueIsLTPow2(const Expr *E, unsigned P, CheckerContext &C) { - if (P >= sizeof(uint64_t) * 8) - return true; - SValBuilder &SVB = C.getSValBuilder(); - const NonLoc &B = SVB.makeIntVal((uint64_t)1 << P, true); - - ProgramStateRef State = C.getState(); - const SVal &V = C.getSVal(E); - auto LT = SVB.evalBinOp(State, BO_LT, V, B, E->getType()); - return !State->assume(LT.castAs(), false); -} - void PointerAlignmentChecker::checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const { int LeftTZ = getTrailingZerosCount(BO->getLHS(), C); @@ -744,7 +834,9 @@ void PointerAlignmentChecker::checkPostStmt(const BinaryOperator *BO, if (!ResVal.isUnknown() && !ResVal.getAsSymbol()) return; + ASTContext &ASTCtx = C.getASTContext(); const SVal &RHSVal = C.getSVal(BO->getRHS()); + unsigned BaseAlignFromOffsetOf; int BitWidth = C.getASTContext().getTypeSize(BO->getType()); int Res = 0; int RHSConst = 0; @@ -770,15 +862,20 @@ void PointerAlignmentChecker::checkPostStmt(const BinaryOperator *BO, case clang::BO_OrAssign: Res = std::min(LeftTZ, RightTZ); break; - case clang::BO_Add: - case clang::BO_AddAssign: case clang::BO_Sub: case clang::BO_SubAssign: + BaseAlignFromOffsetOf = getBaseAlignFromOffsetOf(BO->getRHS(), ASTCtx); + if (BaseAlignFromOffsetOf > 0) { + Res = APSInt::getUnsigned(BaseAlignFromOffsetOf).countTrailingZeros(); + break; + } + [[clang::fallthrough]]; + case clang::BO_Add: + case clang::BO_AddAssign: if (BO->getLHS()->getType()->isPointerType()) { const QualType &PointeeTy = BO->getLHS()->getType()->getPointeeType(); - const CharUnits A = C.getASTContext().getTypeAlignInChars(PointeeTy); - RightTZ += - llvm::APSInt::getUnsigned(A.getQuantity()).countTrailingZeros(); + const CharUnits A = ASTCtx.getTypeAlignInChars(PointeeTy); + RightTZ += APSInt::getUnsigned(A.getQuantity()).countTrailingZeros(); } Res = std::min(LeftTZ, RightTZ); break; @@ -853,8 +950,8 @@ void printAlign(raw_ostream &OS, unsigned TZC) { void describeOriginalAllocation(const ValueDecl *SrcDecl, PathDiagnosticLocation SrcLoc, - PathSensitiveBugReport &W, - ASTContext &ASTCtx) { + PathSensitiveBugReport &W, ASTContext &ASTCtx, + unsigned FragileAlign) { SmallString<350> Note; llvm::raw_svector_ostream OS2(Note); const QualType &AllocType = SrcDecl->getType().getCanonicalType(); @@ -866,6 +963,8 @@ void describeOriginalAllocation(const ValueDecl *SrcDecl, OS2 << "1 byte"; else OS2 << Align << " bytes"; + if (FragileAlign > Align) + OS2 << " (fragile alignment " << FragileAlign << " bytes)"; W.addNote(Note, SrcLoc); } @@ -885,7 +984,9 @@ PointerAlignmentChecker::emitAlignmentWarning( const ValueDecl *MRDecl = nullptr; PathDiagnosticLocation MRDeclLoc; + unsigned FragileAlignment = 0; if (const MemRegion *MR = UnderalignedPtrVal.getAsRegion()) { + FragileAlignment = getFragileAlignment(MR, C.getState(), C.getASTContext()); if (const DeclRegion *OriginalAlloc = getOriginalAllocation(MR)) { MRDecl = OriginalAlloc->getDecl(); MRDeclLoc = PathDiagnosticLocation::create(MRDecl, C.getSourceManager()); @@ -900,11 +1001,13 @@ PointerAlignmentChecker::emitAlignmentWarning( W->addVisitor(std::make_unique(S)); if (MRDecl) { - describeOriginalAllocation(MRDecl, MRDeclLoc, *W, C.getASTContext()); + describeOriginalAllocation(MRDecl, MRDeclLoc, *W, C.getASTContext(), + FragileAlignment); } if (CapStorageDecl) { - auto CapSrcDeclLoc = PathDiagnosticLocation::create(CapStorageDecl, C.getSourceManager()); + auto CapSrcDeclLoc = + PathDiagnosticLocation::create(CapStorageDecl, C.getSourceManager()); W->addNote(CapStorageMsg, CapSrcDeclLoc); } @@ -980,16 +1083,19 @@ PathDiagnosticPieceRef PointerAlignmentChecker::CapStorageBugVisitor::VisitNode( ProgramStateRef State = N->getState(); ProgramStateRef Pred = N->getFirstPred()->getState(); - if (!State->contains(MemReg) || Pred->contains(MemReg)) + if (!State->contains(MemReg) || + Pred->contains(MemReg)) return nullptr; SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); if (const CastExpr *CE = dyn_cast(S)) { - if (!CE->getType()->isPointerType() || isGenericPointerType(CE->getType(), true)) + if (!CE->getType()->isPointerType() || + isGenericPointerType(CE->getType(), true)) return nullptr; const QualType &DstPTy = CE->getType()->getPointeeType(); - if (!DstPTy->isIncompleteType() || !hasCapability(DstPTy, N->getCodeDecl().getASTContext())) + if (!DstPTy->isIncompleteType() || + !hasCapability(DstPTy, N->getCodeDecl().getASTContext())) return nullptr; OS << "Cast to capability-containing type"; } else if (isa(S)) { @@ -998,7 +1104,8 @@ PathDiagnosticPieceRef PointerAlignmentChecker::CapStorageBugVisitor::VisitNode( return nullptr; // Generate the extra diagnostic. - PathDiagnosticLocation const Pos(S, BRC.getSourceManager(), N->getLocationContext()); + PathDiagnosticLocation const Pos(S, BRC.getSourceManager(), + N->getLocationContext()); return std::make_shared(Pos, OS.str(), true); } diff --git a/clang/test/Analysis/pointer-alignment.c b/clang/test/Analysis/pointer-alignment.c index 86c824e63f67..369f0af2ce14 100644 --- a/clang/test/Analysis/pointer-alignment.c +++ b/clang/test/Analysis/pointer-alignment.c @@ -78,6 +78,12 @@ int voidptr_cast(int *ip1, int *ip2) { return b1 || b2; } +intptr_t param(int *pI, char* pC) { + intptr_t *ipI = (intptr_t*)pI; // expected-warning{{Pointer value aligned to a 4 byte boundary cast to type 'intptr_t * __capability' with 16-byte capability alignment}} + intptr_t *ipC = (intptr_t*)pC; // no warn + return ipI - ipC; +} + typedef struct B { long *ptr; long flex[1]; // expected-note{{Original allocation}} @@ -149,6 +155,10 @@ void gen_storage(struct T *pT, void *p, size_t n) { void *m = malloc(n); memcpy(a6, m, n); // no-warn copy(a6, m, n); + + + int x; + memcpy(&x, p, sizeof(int)); // no warn } // ---- @@ -167,3 +177,19 @@ void test_assign(struct T *pT) { pT->pVoid = (void*)"string"; // no warning } + + +#define offsetof(T,F) __builtin_offsetof(T, F) + +struct S2 { + char c[10]; + short sh; + int x; +}; +int test_offsetof(char *pC, short *pSh, struct S2 *pS2) { + struct S2 *q = (struct S2*)((char*)pSh - offsetof(struct S2, sh)); // no-warn + short* pSh2 = &pS2->sh; + struct S2 *q2 = (struct S2*)((char*)pSh2 - offsetof(struct S2, sh)); + struct S2 *q3 = (struct S2*)((char*)pC - offsetof(struct S2, c)); // no-warn + return q->x + q2->x + q3->x; +} From f95c3e43c1e72f5f2c2be7617029c8ec7d4ef48d Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 20 Mar 2024 16:27:37 +0000 Subject: [PATCH 52/77] [CHERI_CSA] CapabilityCopyChecker: ReportForCharPtr=false by default --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 2 +- .../Checkers/CHERI/capability-copy-hybrid.c | 14 +++++---- .../Checkers/CHERI/capability-copy-purecap.c | 30 +++++++++++-------- clang/test/Analysis/analyzer-config.c | 2 +- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 742c10703ad0..d84e0e88b8cd 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1774,7 +1774,7 @@ def CapabilityCopyChecker : Checker<"CapabilityCopy">, "Report tag-stripping copy for char* function parameters. " "Suppression of warnings for C-strings is used to reduce " "the number of false alarms, but it's not very reliable.", - "true", + "false", Released> ]>, Documentation; diff --git a/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c b/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c index 89e975cc9f37..bab67b584d48 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c @@ -12,6 +12,8 @@ extern void * malloc(size_t); extern void *memmove(void *dest, const void *src, size_t n); extern void free(void *ptr); +typedef unsigned long uintptr_t; + static int x; // ===== Tag-stripping copy ===== @@ -30,7 +32,7 @@ void copy_intptr_byte2(int *px, int **ppy) { *(char*)ppy = *s; } -static void swapfunc(void *a, void *b, int n) { +void swapfunc(void *a, void *b, int n) { long i = n; char *pi = (char *)(a); char *pj = (char *)(b); @@ -57,7 +59,9 @@ void memcpy_impl_unaligned(void* src0, void *dst0, size_t len) { char *src = src0; char *dst = dst0; - if ((len < sizeof(BLOCK_TYPE)) || ((long)src & (BLOCK_SIZE - 1)) || ((long)dst & (BLOCK_SIZE - 1))) + if ((len < sizeof(BLOCK_TYPE)) || + ((uintptr_t)src & (BLOCK_SIZE - 1)) || + ((uintptr_t)dst & (BLOCK_SIZE - 1))) while (len--) *dst++ = *src++; } @@ -81,7 +85,7 @@ void voidptr_arg_store1(void *q) { *s = 42; } -// ===== Part of capability representation used as argument in binary operator ===== +// === Part of capability representation used as argument in binary operator === int hash_no_call(void *key0, size_t len) { char *k = key0; @@ -124,7 +128,7 @@ int memcmp_impl(const void* m1, const void *m2, size_t len) { return 0; } -int ptr_cmp(int *x, int *y) { - return memcmp_impl(&x, &y, sizeof(int*)); +int ptr_cmp(int *px, int *py) { + return memcmp_impl(&px, &py, sizeof(int*)); } diff --git a/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c b/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c index bad1d06cf7f6..3887992c6dce 100644 --- a/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c @@ -1,5 +1,6 @@ // RUN: %cheri_purecap_cc1 -analyze -verify %s \ -// RUN: -analyzer-checker=core,cheri.CapabilityCopy +// RUN: -analyzer-checker=core,cheri.CapabilityCopy \ +// RUN: -analyzer-config cheri.CapabilityCopy:ReportForCharPtr=true typedef __intcap_t intptr_t; typedef __uintcap_t uintptr_t; @@ -9,7 +10,7 @@ typedef __uintcap_t uintptr_t; typedef __typeof__(sizeof(int)) size_t; extern void * malloc(size_t); -extern void *memmove(void *dest, const void *src, size_t n); +extern void * memmove(void *dest, const void *src, size_t n); extern void free(void *ptr); static int x; @@ -47,7 +48,7 @@ void copy_intptr(int **ppy) { // ===== Pointer to capability passed as void* ==== -static void swapfunc(void *a, void *b, int n) { +void swapfunc(void *a, void *b, int n) { long i = n; char *pi = (char *)(a); char *pj = (char *)(b); @@ -71,7 +72,9 @@ void memcpy_impl_unaligned(void* src0, void *dst0, size_t len) { char *src = src0; char *dst = dst0; - if ((len < sizeof(BLOCK_TYPE)) || ((long)src & (BLOCK_SIZE - 1)) || ((long)dst & (BLOCK_SIZE - 1))) + if ((len < sizeof(BLOCK_TYPE)) || + ((uintptr_t)src & (BLOCK_SIZE - 1)) || + ((uintptr_t)dst & (BLOCK_SIZE - 1))) while (len--) *dst++ = *src++; // expected-warning{{Tag-stripping store of a capability}} } @@ -92,21 +95,22 @@ struct S { }; void struct_field(void *p) { + int x1; struct S *ps = p; ps->p = malloc(10*sizeof(int)); - int x = ps->x; - *ps->p = x; // no warning + x1 = ps->x; + *ps->p = x1; // no warning } -char fp_malloc() { +char fp_malloc(void) { void *q = malloc(100); char *s = (char*)q; // no warning return *s; } char fp_init_str(void) { - char s[] = "String literal"; // no warning - return s[3]; + char sl[] = "String literal"; // no warning + return sl[3]; } void copyAsLong(void* src0, void *dst0, size_t len) { @@ -128,6 +132,7 @@ void char_ptr(char* src, char *dst, size_t len) { #define EOL 10 void c_string(char* src1, char* src2, char* src3, char *src4, char *dst) { int i = 0; + char c; while (src1[i]) *dst++ = src1[i++]; // no warning @@ -135,7 +140,6 @@ void c_string(char* src1, char* src2, char* src3, char *src4, char *dst) { while (*src2 != '.') *dst++ = *src2++; // no warning - char c; while ((c = *src3++)) *dst++ = c; // no warning @@ -146,7 +150,7 @@ void c_string(char* src1, char* src2, char* src3, char *src4, char *dst) { extern size_t strlen(const char *s); void strcpy_impl(char* src, char *dst) { size_t len = strlen(src); - for (int i=0; i < len; i++) + for (unsigned int i=0; i < len; i++) *dst++ = *src++; // no warning } @@ -202,7 +206,7 @@ int memcmp_impl(const void* m1, const void *m2, size_t len) { return 0; } -int ptr_cmp(int *x, int *y) { - return memcmp_impl(&x, &y, sizeof(int*)); +int ptr_cmp(int *px, int *py) { + return memcmp_impl(&px, &py, sizeof(int*)); } diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index e9683063172c..d13a4e0a847f 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -33,7 +33,7 @@ // CHECK-NEXT: cfg-rich-constructors = true // CHECK-NEXT: cfg-scopes = false // CHECK-NEXT: cfg-temporary-dtors = true -// CHECK-NEXT: cheri.CapabilityCopy:ReportForCharPtr = true +// CHECK-NEXT: cheri.CapabilityCopy:ReportForCharPtr = false // CHECK-NEXT: cheri.ProvenanceSource:ReportForAmbiguousProvenance = true // CHECK-NEXT: cheri.ProvenanceSource:ShowFixIts = false // CHECK-NEXT: consider-single-element-arrays-as-flexible-array-members = false From 9a8a68776b118f4905b0b3dce020b7d7c9f6ea84 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 26 Mar 2024 16:26:53 +0000 Subject: [PATCH 53/77] [CHERI_CSA] PointerAlignmentChecker: refine warning types --- .../Checkers/PointerAlignmentChecker.cpp | 66 +++++++++---------- clang/test/Analysis/pointer-alignment.c | 11 +++- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index 91ac792f11eb..8b628aa37eb5 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -47,11 +47,19 @@ class PointerAlignmentChecker : public Checker, check::PostStmt, check::PostStmt, check::Bind, check::PreCall, check::DeadSymbols> { - std::unique_ptr CastAlignBug; - std::unique_ptr CapCastAlignBug; - std::unique_ptr GenPtrEscapeAlignBug; - std::unique_ptr MemcpyAlignBug; - + BugType CastAlignBug{this, "Cast increases required alignment", "Type Error"}; + BugType CapCastAlignBug{this, + "Cast increases required alignment to capability alignment", + "CHERI portability"}; + BugType CapStorageAlignBug{this, + "Not capability-aligned pointer used as capability storage", + "CHERI portability"}; + BugType GenPtrEscapeAlignBug{this, + "Not capability-aligned pointer stored as 'void*'", + "CHERI portability"}; + BugType MemcpyAlignBug{this, + "Copying capabilities through underaligned memory", + "CHERI portability"}; const CallDescriptionMap> MemCpyFn { {{"memcpy", 3}, {0, 1}}, @@ -60,8 +68,6 @@ class PointerAlignmentChecker }; public: - PointerAlignmentChecker(); - void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; void checkPostStmt(const CastExpr *BO, CheckerContext &C) const; void checkPreStmt(const CastExpr *BO, CheckerContext &C) const; @@ -119,21 +125,6 @@ class PointerAlignmentChecker REGISTER_MAP_WITH_PROGRAMSTATE(TrailingZerosMap, SymbolRef, int) REGISTER_SET_WITH_PROGRAMSTATE(CapStorageSet, const MemRegion*) -PointerAlignmentChecker::PointerAlignmentChecker() { - CastAlignBug.reset(new BugType(this, - "Cast increases required alignment", - "Type Error")); - CapCastAlignBug.reset(new BugType(this, - "Cast increases required alignment to capability alignment", - "CHERI portability")); - GenPtrEscapeAlignBug.reset(new BugType(this, - "Not capability-aligned pointer stored as 'void*'", - "CHERI portability")); - MemcpyAlignBug.reset(new BugType(this, - "Copying capabilities through underaligned memory", - "CHERI portability")); -} - namespace { bool isGlobalOrTopLevelArg(const MemSpaceRegion *MS) { @@ -415,8 +406,15 @@ unsigned int getFragileAlignment(const MemRegion *MR, } bool hasCapStorageType(const Expr *E, ASTContext &ASTCtx) { - const QualType &Ty = E->IgnoreCasts()->getType(); - return Ty->isPointerType() && hasCapability(Ty->getPointeeType(), ASTCtx); + const QualType &Ty = E->getType(); + if (!Ty->isPointerType()) + return false; + if (hasCapability(Ty->getPointeeType(), ASTCtx)) + return true; + if (const CastExpr *CE = dyn_cast(E)) { + return hasCapStorageType(CE->getSubExpr(), ASTCtx); + } + return false; } } // namespace @@ -455,7 +453,7 @@ void PointerAlignmentChecker::checkPreStmt(const CastExpr *CE, /* Emit warning */ const QualType &DstTy = CE->getType(); bool DstAlignIsCap = hasCapability(DstTy->getPointeeType(), ASTCtx); - const BugType &BT= DstAlignIsCap ? *CapCastAlignBug : *CastAlignBug; + const BugType &BT= DstAlignIsCap ? CapCastAlignBug : CastAlignBug; SmallString<350> ErrorMessage; llvm::raw_svector_ostream OS(ErrorMessage); @@ -577,11 +575,13 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, << " value is supposed to hold capabilities"; } else OS2 << " this value may be used to hold capabilities"; - emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, - CapStorageReg, CapDstDecl, Note); + emitAlignmentWarning(C, V, + DstIsPtr2CapStorage ? CapStorageAlignBug : GenPtrEscapeAlignBug, + ErrorMessage, CapStorageReg, CapDstDecl, Note); } else - emitAlignmentWarning(C, V, *GenPtrEscapeAlignBug, ErrorMessage, - CapStorageReg); + emitAlignmentWarning(C, V, + DstIsPtr2CapStorage ? CapStorageAlignBug : GenPtrEscapeAlignBug, + ErrorMessage, CapStorageReg); } void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, @@ -658,10 +658,10 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, llvm::raw_svector_ostream OS2(Note); const QualType &AllocType = CapSrcDecl->getType().getCanonicalType(); OS2 << "Capabilities stored in " << "'" << AllocType.getAsString() << "'"; - emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, + emitAlignmentWarning(C, DstVal, MemcpyAlignBug, ErrorMessage, CapStorageReg, CapSrcDecl, Note); } else - emitAlignmentWarning(C, DstVal, *MemcpyAlignBug, ErrorMessage, + emitAlignmentWarning(C, DstVal, MemcpyAlignBug, ErrorMessage, CapStorageReg); return; } @@ -694,10 +694,10 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, llvm::raw_svector_ostream OS2(Note); const QualType &AllocType = CapDstDecl->getType().getCanonicalType(); OS2 << "Capabilities stored in " << "'" << AllocType.getAsString() << "'"; - emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, + emitAlignmentWarning(C, SrcVal, MemcpyAlignBug, ErrorMessage, CapStorageReg, CapDstDecl, Note); } else - emitAlignmentWarning(C, SrcVal, *MemcpyAlignBug, ErrorMessage, + emitAlignmentWarning(C, SrcVal, MemcpyAlignBug, ErrorMessage, CapStorageReg); return; } diff --git a/clang/test/Analysis/pointer-alignment.c b/clang/test/Analysis/pointer-alignment.c index 369f0af2ce14..9df8dec56b24 100644 --- a/clang/test/Analysis/pointer-alignment.c +++ b/clang/test/Analysis/pointer-alignment.c @@ -178,7 +178,7 @@ void test_assign(struct T *pT) { pT->pVoid = (void*)"string"; // no warning } - +// ---- #define offsetof(T,F) __builtin_offsetof(T, F) struct S2 { @@ -193,3 +193,12 @@ int test_offsetof(char *pC, short *pSh, struct S2 *pS2) { struct S2 *q3 = (struct S2*)((char*)pC - offsetof(struct S2, c)); // no-warn return q->x + q2->x + q3->x; } + +// ---- +void cast_and_assign(void) { + char x[100]; // expected-note{{Original allocation}} + intptr_t *i; + i = (intptr_t*)x; // expected-warning{{Pointer value aligned to a 1 byte boundary cast}} + // no duplicate assign warn + *i = 42; +} From 9f5b173d6cd64aa064874cad2b7dfeafa99b289b Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 26 Mar 2024 17:20:21 +0000 Subject: [PATCH 54/77] [CHERI_CSA] ProvenanceSourceChecker: refine warning types --- .../CHERI/ProvenanceSourceChecker.cpp | 89 +++++++++---------- .../Checkers/CHERI/provenance-source.c | 10 +-- 2 files changed, 48 insertions(+), 51 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index 758876b72813..be3eb0a19bad 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -29,13 +29,28 @@ class ProvenanceSourceChecker : public Checker, check::PostStmt, check::PostStmt, check::DeadSymbols> { - std::unique_ptr AmbigProvAddrArithBugType; - std::unique_ptr AmbigProvWrongOrderBugType; - std::unique_ptr AmbigProvBinOpBugType; + BugType AmbigProvBinOpBugType{this, + "Binary operation with ambiguous provenance", + "CHERI portability"}; + BugType AmbigProvAddrArithBugType{this, + "CHERI-incompatible pointer arithmetic", + "CHERI portability"}; + BugType AmbigProvWrongOrderBugType{this, + "Capability derived from wrong argument", + "CHERI portability"}; - std::unique_ptr AmbigProvAsPtrBugType; - std::unique_ptr InvalidCapPtrBugType; - std::unique_ptr PtrdiffAsIntCapBugType; + BugType AmbigProvAsPtrBugType{this, + "Capability with ambiguous provenance used as pointer", + "CHERI portability"}; + BugType InvalidCapPtrBugType{this, + "NULL-derived capability used as pointer", + "CHERI portability"}; + BugType NoProvPtrBugType{this, + "cheri_no_provenance capability used as pointer", + "CHERI portability"}; + BugType PtrdiffAsIntCapBugType{this, + "Pointer difference as capability", + "CHERI portability"}; private: class InvalidCapBugVisitor : public BugReporterVisitor { @@ -73,8 +88,6 @@ class ProvenanceSourceChecker : public Checker, }; public: - ProvenanceSourceChecker(); - void checkPostStmt(const CastExpr *CE, CheckerContext &C) const; void checkPreStmt(const CastExpr *CE, CheckerContext &C) const; void checkPostStmt(const BinaryOperator *BO, CheckerContext &C) const; @@ -113,34 +126,6 @@ REGISTER_SET_WITH_PROGRAMSTATE(AmbiguousProvenanceSym, SymbolRef) REGISTER_SET_WITH_PROGRAMSTATE(AmbiguousProvenanceReg, const MemRegion *) REGISTER_TRAIT_WITH_PROGRAMSTATE(Ptr2IntCapId, unsigned) -ProvenanceSourceChecker::ProvenanceSourceChecker() { - AmbigProvBinOpBugType.reset( - new BugType(this, - "Binary operation with ambiguous provenance", - "CHERI portability")); - AmbigProvAddrArithBugType.reset( - new BugType(this, - "CHERI-incompatible pointer arithmetic", - "CHERI portability")); - AmbigProvWrongOrderBugType.reset( - new BugType(this, - "Capability derived from wrong argument", - "CHERI portability")); - - AmbigProvAsPtrBugType.reset( - new BugType(this, - "Capability with ambiguous provenance used as pointer", - "CHERI portability")); - InvalidCapPtrBugType.reset( - new BugType(this, - "Invalid capability used as pointer", - "CHERI portability")); - PtrdiffAsIntCapBugType.reset( - new BugType(this, - "Pointer difference as capability", - "CHERI portability")); -} - static bool isIntegerToIntCapCast(const CastExpr *CE) { if (CE->getCastKind() != CK_IntegralCast) return false; @@ -232,6 +217,14 @@ static bool isIntToVoidPtrCast(const CastExpr *CE) { return false; } +static bool isNoProvToPtrCast(const CastExpr *CE) { + if (!CE->getType()->isPointerType()) + return false; + + const Expr *Src = CE->getSubExpr(); + return Src->getType()->hasAttr(attr::CHERINoProvenance); +} + // Report intcap with ambiguous or NULL-derived provenance cast to pointer void ProvenanceSourceChecker::checkPreStmt(const CastExpr *CE, CheckerContext &C) const { @@ -250,16 +243,20 @@ void ProvenanceSourceChecker::checkPreStmt(const CastExpr *CE, if (!ErrNode) return; R = std::make_unique( - *AmbigProvAsPtrBugType, + AmbigProvAsPtrBugType, "Capability with ambiguous provenance is used as pointer", ErrNode); - } else if (hasNoProvenance(State, SrcVal) && !SrcVal.isConstant() - && !isIntToVoidPtrCast(CE)) { + } else if (hasNoProvenance(State, SrcVal) && !SrcVal.isConstant()) { ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); if (!ErrNode) return; - R = std::make_unique( - *InvalidCapPtrBugType, "Invalid capability is used as pointer", - ErrNode); + if (isNoProvToPtrCast(CE)) + R = std::make_unique( + NoProvPtrBugType, "cheri_no_provenance capability used as pointer", + ErrNode); + else + R = std::make_unique( + InvalidCapPtrBugType, "NULL-derived capability used as pointer", + ErrNode); if (SymbolRef S = SrcVal.getAsSymbol()) R->addVisitor(std::make_unique(S)); } else @@ -287,7 +284,7 @@ ExplodedNode *ProvenanceSourceChecker::emitPtrdiffAsIntCapWarn( if (!ErrNode) return nullptr; auto R = std::make_unique( - *PtrdiffAsIntCapBugType, "Pointer difference as capability", + PtrdiffAsIntCapBugType, "Pointer difference as capability", ErrNode); R->addRange(BO->getSourceRange()); @@ -337,7 +334,7 @@ const BugType &ProvenanceSourceChecker::explainWarning( OS << "LHS and RHS were derived from pointers." " Result capability will be derived from LHS by default." " This code may need to be rewritten for CHERI."; - return *AmbigProvAddrArithBugType; + return AmbigProvAddrArithBugType; } if (LHSIsNullDerived) { @@ -352,7 +349,7 @@ const BugType &ProvenanceSourceChecker::explainWarning( << (BO->getType()->isUnsignedIntegerType() ? "size_t" : "ptrdiff_t") << "'. "; } - return *AmbigProvWrongOrderBugType; + return AmbigProvWrongOrderBugType; } OS << "; consider indicating the provenance-carrying argument " @@ -374,7 +371,7 @@ const BugType &ProvenanceSourceChecker::explainWarning( } } - return *AmbigProvBinOpBugType; + return AmbigProvBinOpBugType; } ExplodedNode *ProvenanceSourceChecker::emitAmbiguousProvenanceWarn( diff --git a/clang/test/Analysis/Checkers/CHERI/provenance-source.c b/clang/test/Analysis/Checkers/CHERI/provenance-source.c index 554a7d790eec..35465765ba88 100644 --- a/clang/test/Analysis/Checkers/CHERI/provenance-source.c +++ b/clang/test/Analysis/Checkers/CHERI/provenance-source.c @@ -107,7 +107,7 @@ char add_var(int *p, int x, int c) { int * null_derived(int x) { intptr_t u = (intptr_t)x; - return (int*)u; // expected-warning{{Invalid capability is used as pointer}} + return (int*)u; // expected-warning{{NULL-derived capability used as pointer}} } uintptr_t fn1(char *str, int f) { @@ -133,14 +133,14 @@ char* ptr_diff(char *s1, char *s2) { intptr_t a = (intptr_t)s1; intptr_t b = (intptr_t)s2; intptr_t d = a - b; // expected-warning{{Pointer difference as capability}} - return (char*)d; // expected-warning{{Invalid capability is used as pointer}} + return (char*)d; // expected-warning{{NULL-derived capability used as pointer}} } char* ptr_diff2(int x, char *s) { intptr_t a = (intptr_t)x; intptr_t b = (intptr_t)s; intptr_t d = a - b; - return (char*)d; // expected-warning{{Invalid capability is used as pointer}} + return (char*)d; // expected-warning{{NULL-derived capability used as pointer}} } void *fp2(char *p) { @@ -175,13 +175,13 @@ uintptr_t fn2(char *a, char *b) { char *ptrdiff(char *a, unsigned x) { intptr_t ip = ((ptrdiff_t)a | (intptr_t)x); - char *p = (char*) ip; // expected-warning{{Invalid capability is used as pointer}} + char *p = (char*) ip; // expected-warning{{NULL-derived capability used as pointer}} return p; } int fp5(char *a, unsigned x) { void *p = (void*)(uintptr_t)a; - void *q = (void*)(uintptr_t)x; // common pattern, don't fire warning + void *q = (void*)(uintptr_t)x; // expected-warning{{cheri_no_provenance capability used as pointer}} return (char*)p - (char*)q; } From 0c833f174c89a0b9c538a970f1591c5b60f3b8bf Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 26 Mar 2024 17:28:45 +0000 Subject: [PATCH 55/77] [CHERI_CSA] PointerAlignmentChecker: support bcopy --- clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index 8b628aa37eb5..e0904cc24de4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -65,6 +65,7 @@ class PointerAlignmentChecker {{"memcpy", 3}, {0, 1}}, {{"mempcpy", 3}, {0, 1}}, {{"memmove", 3}, {0, 1}}, + {{"bcopy", 3}, {1, 0}}, }; public: From f45ac19fe20b3f6b9c41b7ef94cd14115c4d6614 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 27 Mar 2024 15:04:48 +0000 Subject: [PATCH 56/77] [CHERI_CSA] ProvenanceSourceChecker: delete ptrdiff as capability warning --- .../CHERI/ProvenanceSourceChecker.cpp | 84 ++++++------------- .../Checkers/CHERI/provenance-source.c | 2 +- 2 files changed, 25 insertions(+), 61 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index be3eb0a19bad..e10bba317738 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -12,12 +12,12 @@ //===----------------------------------------------------------------------===// #include "CHERIUtils.h" +#include #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include -#include using namespace clang; using namespace ento; @@ -48,10 +48,7 @@ class ProvenanceSourceChecker : public Checker, BugType NoProvPtrBugType{this, "cheri_no_provenance capability used as pointer", "CHERI portability"}; - BugType PtrdiffAsIntCapBugType{this, - "Pointer difference as capability", - "CHERI portability"}; - + private: class InvalidCapBugVisitor : public BugReporterVisitor { public: @@ -110,9 +107,6 @@ class ProvenanceSourceChecker : public Checker, bool RHSIsAddr, bool RHSIsNullDerived) const; - ExplodedNode *emitPtrdiffAsIntCapWarn(const BinaryOperator *BO, - CheckerContext &C) const; - static void propagateProvenanceInfo(ExplodedNode *N, const Expr *E, CheckerContext &C, @@ -126,7 +120,8 @@ REGISTER_SET_WITH_PROGRAMSTATE(AmbiguousProvenanceSym, SymbolRef) REGISTER_SET_WITH_PROGRAMSTATE(AmbiguousProvenanceReg, const MemRegion *) REGISTER_TRAIT_WITH_PROGRAMSTATE(Ptr2IntCapId, unsigned) -static bool isIntegerToIntCapCast(const CastExpr *CE) { +namespace { +bool isIntegerToIntCapCast(const CastExpr *CE) { if (CE->getCastKind() != CK_IntegralCast) return false; if (!CE->getType()->isIntCapType() || @@ -135,7 +130,7 @@ static bool isIntegerToIntCapCast(const CastExpr *CE) { return true; } -static bool isPointerToIntCapCast(const CastExpr *CE) { +bool isPointerToIntCapCast(const CastExpr *CE) { if (CE->getCastKind() != clang::CK_PointerToIntegral) return false; if (!CE->getType()->isIntCapType()) @@ -143,6 +138,8 @@ static bool isPointerToIntCapCast(const CastExpr *CE) { return true; } +} // namespace + void ProvenanceSourceChecker::checkPostStmt(const CastExpr *CE, CheckerContext &C) const { if (!isPureCapMode(C.getASTContext())) @@ -168,7 +165,9 @@ void ProvenanceSourceChecker::checkPostStmt(const CastExpr *CE, } } -static bool hasAmbiguousProvenance(ProgramStateRef State, const SVal &Val) { +namespace { + +bool hasAmbiguousProvenance(ProgramStateRef State, const SVal &Val) { if (SymbolRef Sym = Val.getAsSymbol()) return State->contains(Sym); @@ -178,7 +177,7 @@ static bool hasAmbiguousProvenance(ProgramStateRef State, const SVal &Val) { return false; } -static bool hasNoProvenance(ProgramStateRef State, const SVal &Val) { +bool hasNoProvenance(ProgramStateRef State, const SVal &Val) { if (Val.isConstant()) return true; @@ -191,7 +190,7 @@ static bool hasNoProvenance(ProgramStateRef State, const SVal &Val) { return false; } -static bool isAddress(const SVal &Val) { +bool isAddress(const SVal &Val) { if (!Val.getAsLocSymbol(true)) return false; @@ -201,23 +200,7 @@ static bool isAddress(const SVal &Val) { return true; } -static bool isIntToVoidPtrCast(const CastExpr *CE) { - if (!CE->getType()->isVoidPointerType()) - return false; - - const Expr *Src = CE->getSubExpr(); - if (!Src->getType()->isIntCapType()) - return false; - - if (auto *CE2 = dyn_cast(Src)) { - const QualType &T = CE2->getSubExpr()->getType(); - return T->isIntegerType() && !T->isIntCapType(); - } - - return false; -} - -static bool isNoProvToPtrCast(const CastExpr *CE) { +bool isNoProvToPtrCast(const CastExpr *CE) { if (!CE->getType()->isPointerType()) return false; @@ -225,6 +208,8 @@ static bool isNoProvToPtrCast(const CastExpr *CE) { return Src->getType()->hasAttr(attr::CHERINoProvenance); } +} // namespace + // Report intcap with ambiguous or NULL-derived provenance cast to pointer void ProvenanceSourceChecker::checkPreStmt(const CastExpr *CE, CheckerContext &C) const { @@ -267,7 +252,9 @@ void ProvenanceSourceChecker::checkPreStmt(const CastExpr *CE, C.emitReport(std::move(R)); } -static bool justConverted2IntCap(Expr *E, const ASTContext &Ctx) { +namespace { + +bool justConverted2IntCap(Expr *E, const ASTContext &Ctx) { assert(E->getType()->isCHERICapabilityType(Ctx, true)); if (auto *CE = dyn_cast(E)) { const QualType OrigType = CE->getSubExpr()->getType(); @@ -277,32 +264,7 @@ static bool justConverted2IntCap(Expr *E, const ASTContext &Ctx) { return false; } -ExplodedNode *ProvenanceSourceChecker::emitPtrdiffAsIntCapWarn( - const BinaryOperator *BO, CheckerContext &C) const { - // Generate the report. - ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); - if (!ErrNode) - return nullptr; - auto R = std::make_unique( - PtrdiffAsIntCapBugType, "Pointer difference as capability", - ErrNode); - R->addRange(BO->getSourceRange()); - - const SVal &LHSVal = C.getSVal(BO->getLHS()); - R->markInteresting(LHSVal); - if (const MemRegion *Reg = LHSVal.getAsRegion()) - R->addVisitor(std::make_unique(Reg)); - - const SVal &RHSVal = C.getSVal(BO->getRHS()); - R->markInteresting(RHSVal); - if (const MemRegion *Reg = RHSVal.getAsRegion()) - R->addVisitor(std::make_unique(Reg)); - C.emitReport(std::move(R)); - return ErrNode; -} - -namespace { FixItHint addFixIt(const Expr *NDOp, CheckerContext &C, bool IsUnsigned) { const SourceRange &SrcRange = NDOp->getSourceRange(); @@ -498,9 +460,7 @@ void ProvenanceSourceChecker::checkPostStmt(const BinaryOperator *BO, N = C.getPredecessor(); InvalidCap = false; } else if (IsSub && LHSIsAddr && RHSIsAddr) { - N = emitPtrdiffAsIntCapWarn(BO, C); - if (!N) - N = C.getPredecessor(); + N = C.getPredecessor(); InvalidCap = true; } else if (LHSIsNullDerived && (RHSIsNullDerived || IsSub)) { N = C.getPredecessor(); @@ -562,6 +522,8 @@ void ProvenanceSourceChecker::checkDeadSymbols(SymbolReaper &SymReaper, C.addTransition(State); } +namespace { + static void describeCast(raw_ostream &OS, const CastExpr *CE, const LangOptions &LangOpts) { OS << (dyn_cast(CE) ? "implicit" : "explicit"); @@ -572,6 +534,8 @@ static void describeCast(raw_ostream &OS, const CastExpr *CE, OS << "'"; } +} // namespace + PathDiagnosticPieceRef ProvenanceSourceChecker::InvalidCapBugVisitor::VisitNode( const ExplodedNode *N, BugReporterContext &BRC, diff --git a/clang/test/Analysis/Checkers/CHERI/provenance-source.c b/clang/test/Analysis/Checkers/CHERI/provenance-source.c index 35465765ba88..9aae98bcc36a 100644 --- a/clang/test/Analysis/Checkers/CHERI/provenance-source.c +++ b/clang/test/Analysis/Checkers/CHERI/provenance-source.c @@ -132,7 +132,7 @@ uintptr_t align_down(void *p, size_t alignment) { char* ptr_diff(char *s1, char *s2) { intptr_t a = (intptr_t)s1; intptr_t b = (intptr_t)s2; - intptr_t d = a - b; // expected-warning{{Pointer difference as capability}} + intptr_t d = a - b; return (char*)d; // expected-warning{{NULL-derived capability used as pointer}} } From 4cf183689a80b96d8a93ad3ae00e3602c7e3a0cb Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 2 Apr 2024 15:55:25 +0100 Subject: [PATCH 57/77] [CHERI_CSA] ProvenanceSourceChecker: Fix for CompoundAssignmentOp --- .../Checkers/CHERI/ProvenanceSourceChecker.cpp | 14 +++++++++----- .../Analysis/Checkers/CHERI/provenance-source.c | 8 +++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index e10bba317738..81fe948e5c0a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -264,8 +264,6 @@ bool justConverted2IntCap(Expr *E, const ASTContext &Ctx) { return false; } - - FixItHint addFixIt(const Expr *NDOp, CheckerContext &C, bool IsUnsigned) { const SourceRange &SrcRange = NDOp->getSourceRange(); bool InValidStr = true; @@ -279,6 +277,14 @@ FixItHint addFixIt(const Expr *NDOp, CheckerContext &C, bool IsUnsigned) { return {}; } +bool isArith(BinaryOperatorKind OpCode) { + if (BinaryOperator::isCompoundAssignmentOp(OpCode)) + return isArith(BinaryOperator::getOpForCompoundAssignment(OpCode)); + return BinaryOperator::isAdditiveOp(OpCode) || + BinaryOperator::isMultiplicativeOp(OpCode) || + BinaryOperator::isBitwiseOp(OpCode); +} + } // namespace const BugType &ProvenanceSourceChecker::explainWarning( @@ -414,9 +420,7 @@ void ProvenanceSourceChecker::checkPostStmt(const BinaryOperator *BO, return; BinaryOperatorKind const OpCode = BO->getOpcode(); - if (!(BinaryOperator::isAdditiveOp(OpCode) - || BinaryOperator::isMultiplicativeOp(OpCode) - || BinaryOperator::isBitwiseOp(OpCode))) + if (!isArith(OpCode)) return; bool const IsSub = OpCode == clang::BO_Sub || OpCode == clang::BO_SubAssign; diff --git a/clang/test/Analysis/Checkers/CHERI/provenance-source.c b/clang/test/Analysis/Checkers/CHERI/provenance-source.c index 9aae98bcc36a..22f16d0dd6e4 100644 --- a/clang/test/Analysis/Checkers/CHERI/provenance-source.c +++ b/clang/test/Analysis/Checkers/CHERI/provenance-source.c @@ -185,10 +185,16 @@ int fp5(char *a, unsigned x) { return (char*)p - (char*)q; } -char* const2ptr(int *p, int x) { +char* const2ptr(void) { return (char*)(-1); } +void *fn3(size_t x, int y) { + intptr_t a = (intptr_t)x; + a += y; + return (void*)a; // expected-warning{{NULL-derived capability used as pointer}} +} + //------------------- Inter-procedural warnings --------------------- static int *p; From 76fe8227a36973e4156c9d142079ada78038cfe4 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 4 Apr 2024 13:29:05 +0100 Subject: [PATCH 58/77] [CHERI_CSA] CapabilityCopyChecker: fix for BugType --- .../Checkers/CHERI/CapabilityCopyChecker.cpp | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index 6a0016b4ac7c..a88d2a1ee3df 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -59,8 +59,12 @@ class CapabilityCopyChecker :public Checker { - std::unique_ptr UseCapAsNonCap; - std::unique_ptr StoreCapAsNonCap; + BugType UseCapAsNonCap{this, + "Part of capability value used in binary operator", + "CHERI portability"}; + BugType StoreCapAsNonCap{this, + "Tag-stripping copy of capability", + "CHERI portability"}; using CheckFn = std::function; @@ -72,12 +76,9 @@ class CapabilityCopyChecker :public Checker( - *StoreCapAsNonCap, "Tag-stripping store of a capability", ErrNode); + StoreCapAsNonCap, "Tag-stripping store of a capability", ErrNode); W->addRange(S->getSourceRange()); C.emitReport(std::move(W)); } @@ -566,7 +556,7 @@ ExplodedNode *CapabilityCopyChecker::checkBinaryOpArg(CheckerContext &C, /* Pointer to capability passed as void* argument */ if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { auto W = std::make_unique( - *UseCapAsNonCap, + UseCapAsNonCap, "Part of capability representation used as argument in binary " "operator", ErrNode); From fbc8f2e8c1b091814938ba8178e316b2dcfb91f6 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 28 May 2024 17:19:45 +0100 Subject: [PATCH 59/77] [CHERI_CSA] ProvenanceSourceChecker: refine warning types --- .../CHERI/ProvenanceSourceChecker.cpp | 33 ++++++++++++------- .../Checkers/CHERI/provenance-source.c | 16 +++++++-- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index 81fe948e5c0a..86eddf53073a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -19,6 +19,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + using namespace clang; using namespace ento; using namespace cheri; @@ -45,8 +46,8 @@ class ProvenanceSourceChecker : public Checker, BugType InvalidCapPtrBugType{this, "NULL-derived capability used as pointer", "CHERI portability"}; - BugType NoProvPtrBugType{this, - "cheri_no_provenance capability used as pointer", + BugType ProvenanceLossBugType{this, + "NULL-derived capability: loss of provenance", "CHERI portability"}; private: @@ -130,6 +131,14 @@ bool isIntegerToIntCapCast(const CastExpr *CE) { return true; } +bool isPointerToIntegerCast(const CastExpr *CE) { + if (CE->getCastKind() != CK_PointerToIntegral) + return false; + if (CE->getType()->isIntCapType()) + return false; + return true; +} + bool isPointerToIntCapCast(const CastExpr *CE) { if (CE->getCastKind() != clang::CK_PointerToIntegral) return false; @@ -231,12 +240,14 @@ void ProvenanceSourceChecker::checkPreStmt(const CastExpr *CE, AmbigProvAsPtrBugType, "Capability with ambiguous provenance is used as pointer", ErrNode); } else if (hasNoProvenance(State, SrcVal) && !SrcVal.isConstant()) { + if (isNoProvToPtrCast(CE)) + return; // intentional ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); if (!ErrNode) return; - if (isNoProvToPtrCast(CE)) + if (SrcVal.getAs()) R = std::make_unique( - NoProvPtrBugType, "cheri_no_provenance capability used as pointer", + ProvenanceLossBugType, "NULL-derived capability: loss of provenance", ErrNode); else R = std::make_unique( @@ -553,16 +564,16 @@ ProvenanceSourceChecker::InvalidCapBugVisitor::VisitNode( llvm::raw_svector_ostream OS(Buf); if (const CastExpr *CE = dyn_cast(S)) { - if (!isIntegerToIntCapCast(CE)) - return nullptr; - if (Sym != N->getSVal(CE).getAsSymbol()) return nullptr; - - if (!N->getState()->contains(Sym)) + if (isIntegerToIntCapCast(CE)) { + if (!N->getState()->contains(Sym)) + return nullptr; + OS << "NULL-derived capability: "; + } else if (isPointerToIntegerCast(CE)) { + OS << "Loss of provenance: "; + } else return nullptr; - - OS << "NULL-derived capability: "; describeCast(OS, CE, BRC.getASTContext().getLangOpts()); } else if (const BinaryOperator *BO = dyn_cast(S)) { BinaryOperatorKind const OpCode = BO->getOpcode(); diff --git a/clang/test/Analysis/Checkers/CHERI/provenance-source.c b/clang/test/Analysis/Checkers/CHERI/provenance-source.c index 22f16d0dd6e4..25dbac755b61 100644 --- a/clang/test/Analysis/Checkers/CHERI/provenance-source.c +++ b/clang/test/Analysis/Checkers/CHERI/provenance-source.c @@ -1,7 +1,8 @@ -// RUN: %cheri_purecap_cc1 -analyze -verify %s \ +// RUN: %cheri_purecap_cc1 -Wcapability-to-integer-cast -analyze -verify %s \ // RUN: -analyzer-checker=core,cheri.ProvenanceSource // RUN: %check_analyzer_fixit %s %t \ // RUN: -triple mips64-unknown-freebsd -target-abi purecap -target-cpu cheri128 -cheri-size 128 \ +// RUN: -Wcapability-to-integer-cast \ // RUN: -analyzer-checker=core,cheri.ProvenanceSource \ // RUN: -analyzer-config cheri.ProvenanceSource:ShowFixIts=true \ // RUN: -verify=non-nested,nested @@ -110,7 +111,7 @@ int * null_derived(int x) { return (int*)u; // expected-warning{{NULL-derived capability used as pointer}} } -uintptr_t fn1(char *str, int f) { +intptr_t fn1(char *str, int f) { str++; intptr_t x = f; return ((intptr_t)str & x); @@ -175,13 +176,14 @@ uintptr_t fn2(char *a, char *b) { char *ptrdiff(char *a, unsigned x) { intptr_t ip = ((ptrdiff_t)a | (intptr_t)x); + // expected-warning@-1{{cast from capability type 'char *' to non-capability, non-address type 'ptrdiff_t' (aka 'long') is most likely an error}} char *p = (char*) ip; // expected-warning{{NULL-derived capability used as pointer}} return p; } int fp5(char *a, unsigned x) { void *p = (void*)(uintptr_t)a; - void *q = (void*)(uintptr_t)x; // expected-warning{{cheri_no_provenance capability used as pointer}} + void *q = (void*)(uintptr_t)x; // no warning -- intentional return (char*)p - (char*)q; } @@ -195,6 +197,14 @@ void *fn3(size_t x, int y) { return (void*)a; // expected-warning{{NULL-derived capability used as pointer}} } +int * loss_of_prov(int *px) { + long x = (long)px; + // expected-warning@-1{{cast from capability type 'int *' to non-capability, non-address type 'long' is most likely an error}} + intptr_t u = (intptr_t)x; + return (int*)u; + // expected-warning@-1{{NULL-derived capability: loss of provenance}} +} + //------------------- Inter-procedural warnings --------------------- static int *p; From fe342afb76f465c65abce7818bedb3ee38238ff0 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 22 Apr 2024 14:54:17 +0100 Subject: [PATCH 60/77] [CHERI_CSA] New alpha.cheri.SubObjectRepresentability checker --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../SubObjectRepresentabilityChecker.cpp | 97 +++++++++++++++++++ .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + .../subobject-representability-morello.c | 22 +++++ 4 files changed, 124 insertions(+) create mode 100644 clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp create mode 100644 clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index d84e0e88b8cd..ac9fbcce4d57 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1787,4 +1787,8 @@ def PointerSizeAssumptionsChecker : Checker<"PointerSizeAssumptions">, let ParentPackage = CHERIAlpha in { +def SubObjectRepresentabilityChecker : Checker<"SubObjectRepresentability">, + HelpText<"TODO: help">, + Documentation; + } // end alpha.cheri diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp new file mode 100644 index 000000000000..42350e3c4f5f --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp @@ -0,0 +1,97 @@ +// ==-- PointerSizeAssumptionsChecker.cpp -------------------------*- 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 checker detects record fields that will not have precise bounds when +// compiled with +// -cheri-bounds=subobject-safe +// due to big size and underaligned offset, as narrowed capability will not +// be representable +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/Support/Morello.h" + +using namespace clang; +using namespace ento; + +namespace { +class SubObjectRepresentabilityChecker + : public Checker> { +public: + void checkASTDecl(const RecordDecl *R, AnalysisManager& mgr, + BugReporter &BR) const; +}; + +} + +void SubObjectRepresentabilityChecker::checkASTDecl(const RecordDecl *R, + AnalysisManager &mgr, + BugReporter &BR) const { + if (!R->isCompleteDefinition()) + return; + + if (!R->getLocation().isValid()) + return; + + /* + SrcMgr::CharacteristicKind Kind = + BR.getSourceManager().getFileCharacteristic(Location); + // Ignore records in system headers + if (Kind != SrcMgr::C_User) + return; + */ + + for (FieldDecl *D : R->fields()) { + QualType T = D->getType(); + + ASTContext &ASTCtx = BR.getContext(); + uint64_t Offset = ASTCtx.getFieldOffset(D)/8; + if (Offset > 0) { + uint64_t Size = ASTCtx.getTypeSize(T)/8; + uint64_t ReqAlign = llvm::getMorelloRequiredAlignment(Size); + if (1 << llvm::countTrailingZeros(Offset) < ReqAlign) { + /* Emit warning */ + SmallString<1024> Err; + llvm::raw_svector_ostream OS(Err); + const PrintingPolicy &PP = ASTCtx.getPrintingPolicy(); + OS << "Field '"; + D->getNameForDiagnostic(OS, PP, false); + OS << "' of type '" << T.getAsString(PP) << "'"; + OS << " (size " << Size << ")"; + OS << " requires " << ReqAlign << " byte alignment for precise bounds;"; + OS << " field offset is " << Offset; + + // Note that this will fire for every translation unit that uses this + // class. This is suboptimal, but at least scan-build will merge + // duplicate HTML reports. + PathDiagnosticLocation L = + PathDiagnosticLocation::createBegin(D, BR.getSourceManager()); + BR.EmitBasicReport(R, this, "Field with imprecise subobject bounds", + "CHERI portability", OS.str(), L); + } + } + } +} + +void ento::registerSubObjectRepresentabilityChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterSubObjectRepresentabilityChecker( + const CheckerManager &mgr) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 887c1eb40f07..aeadda9ff367 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -28,6 +28,7 @@ add_clang_library(clangStaticAnalyzerCheckers CHERI/PointerSizeAssumptionsChecker.cpp CHERI/ProvenanceSourceChecker.cpp CHERI/CapabilityCopyChecker.cpp + CHERI/SubObjectRepresentabilityChecker.cpp ChrootChecker.cpp CloneChecker.cpp ContainerModeling.cpp diff --git a/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c b/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c new file mode 100644 index 000000000000..6f0cce1d8f1d --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c @@ -0,0 +1,22 @@ +// Test for Morello + +// XFAIL: * +// RUN: %clang_cc1 -triple aarch64-none-elf -target-feature +morello -target-feature +c64 -target-abi purecap \ +// RUN: -analyze -analyzer-checker=core,alpha.cheri.SubObjectRepresentability -verify %s + +struct R1 { + struct { + struct { + char c; + char a[0x3FFF]; // no warn + } f1good; + struct { + char c; + char a[0x4000]; // expected-warning{{Field 'a' of type 'char[16384]' (size 16384) requires 8 byte alignment for precise bounds; field offset is 1}} + } f2bad; + struct { + int c[2]; + char a[0x4000]; // no warn + } f3good __attribute__((aligned(8))); + } s2; +} s1; From a90f7a74a9d58147a904889f7ec8b6998bc920bb Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 24 Apr 2024 17:45:09 +0100 Subject: [PATCH 61/77] [CHERI_CSA] SubObjectRepresentability: detailed message --- .../SubObjectRepresentabilityChecker.cpp | 67 +++++++++++++++++-- .../subobject-representability-morello.c | 15 ++++- 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp index 42350e3c4f5f..b6bd8b6ee727 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp @@ -14,6 +14,7 @@ // //===----------------------------------------------------------------------===// +#include "CHERIUtils.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/StmtVisitor.h" #include "clang/ASTMatchers/ASTMatchFinder.h" @@ -23,7 +24,10 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/raw_ostream.h" + #include "llvm/Support/Morello.h" +#include "llvm/CHERI/cheri-compressed-cap/cheri_compressed_cap.h" + using namespace clang; using namespace ento; @@ -31,6 +35,9 @@ using namespace ento; namespace { class SubObjectRepresentabilityChecker : public Checker> { + BugType BT_1{this, "Field with imprecise subobject bounds", + "CHERI portability"}; + public: void checkASTDecl(const RecordDecl *R, AnalysisManager& mgr, BugReporter &BR) const; @@ -63,7 +70,8 @@ void SubObjectRepresentabilityChecker::checkASTDecl(const RecordDecl *R, if (Offset > 0) { uint64_t Size = ASTCtx.getTypeSize(T)/8; uint64_t ReqAlign = llvm::getMorelloRequiredAlignment(Size); - if (1 << llvm::countTrailingZeros(Offset) < ReqAlign) { + uint64_t CurAlign = 1 << llvm::countTrailingZeros(Offset); + if (CurAlign < ReqAlign) { /* Emit warning */ SmallString<1024> Err; llvm::raw_svector_ostream OS(Err); @@ -74,14 +82,61 @@ void SubObjectRepresentabilityChecker::checkASTDecl(const RecordDecl *R, OS << " (size " << Size << ")"; OS << " requires " << ReqAlign << " byte alignment for precise bounds;"; OS << " field offset is " << Offset; - + OS << " (aligned to " << CurAlign << ");"; + + //FIXME: other targets + using Handler = CompressedCap128; + uint64_t ParentSize = ASTCtx.getTypeSize(R->getTypeForDecl())/8; + Handler::addr_t InitLength = Handler ::representable_length(ParentSize); + Handler::cap_t MockCap = Handler::make_max_perms_cap(0, 0, InitLength); + bool exact = Handler::setbounds(&MockCap, Offset, Offset + Size); + assert(!exact); + auto Base = MockCap.base(); + Handler::addr_t Top = MockCap.top(); + OS << " Current bounds: " << Base << "-" << Top; + + PathDiagnosticLocation L = + PathDiagnosticLocation::createBegin(D, BR.getSourceManager()); + auto Report = + std::make_unique(BT_1, OS.str(), L); + Report->setDeclWithIssue(D); + Report->addRange(D->getSourceRange()); + + + auto FI = R->field_begin(); + while (FI != R->field_end() && + ASTCtx.getFieldOffset(*FI)/8 + + ASTCtx.getTypeSize(FI->getType())/8 <= Base) + FI++; + + bool Before = true; + while (FI != R->field_end() && ASTCtx.getFieldOffset(*FI)/8 < Top) { + if (*FI != D) { + SmallString<1024> Note; + llvm::raw_svector_ostream OS2(Note); + + uint64_t CurFieldOffset = ASTCtx.getFieldOffset(*FI)/8; + uint64_t CurFieldSize = ASTCtx.getTypeSize(FI->getType())/8; + uint64_t BytesExposed = + Before ? std::min(CurFieldSize, + CurFieldOffset + CurFieldSize - Base) + : std::min(CurFieldSize, Top - CurFieldOffset); + OS2 << BytesExposed << "/" << CurFieldSize << " bytes exposed"; + if (cheri::hasCapability(FI->getType(), ASTCtx)) + OS2 << " (may expose capability!)"; + + PathDiagnosticLocation LN = + PathDiagnosticLocation::createBegin(*FI, BR.getSourceManager()); + Report->addNote(OS2.str(), LN); + } else + Before = false; + FI++; + } + // Note that this will fire for every translation unit that uses this // class. This is suboptimal, but at least scan-build will merge // duplicate HTML reports. - PathDiagnosticLocation L = - PathDiagnosticLocation::createBegin(D, BR.getSourceManager()); - BR.EmitBasicReport(R, this, "Field with imprecise subobject bounds", - "CHERI portability", OS.str(), L); + BR.emitReport(std::move(Report)); } } } diff --git a/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c b/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c index 6f0cce1d8f1d..f34eb2e74537 100644 --- a/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c +++ b/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c @@ -11,7 +11,7 @@ struct R1 { char a[0x3FFF]; // no warn } f1good; struct { - char c; + char c; // expected-note{{}} char a[0x4000]; // expected-warning{{Field 'a' of type 'char[16384]' (size 16384) requires 8 byte alignment for precise bounds; field offset is 1}} } f2bad; struct { @@ -20,3 +20,16 @@ struct R1 { } f3good __attribute__((aligned(8))); } s2; } s1; + +struct S2 { + int x[3]; + int *px; +}; + +struct R2 { + char x[0x50]; // expected-note{{16/80}} + struct S2 s2; // expected-note{{32/32 bytes exposed (may expose capability!)}} + char c; // expected-note{{1}} + char a[0x8000]; // expected-warning{{Field 'a' of type 'char[32768]'}} + char y[32]; // expected-note{{15/32}} +}; \ No newline at end of file From 69ae32d83743ad7ae7855b04af5a8d5c35d61332 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 25 Apr 2024 14:50:58 +0100 Subject: [PATCH 62/77] [CHERI_CSA] SubObjectRepresentability: disable notes for now --- .../SubObjectRepresentabilityChecker.cpp | 237 ++++++++++++------ .../subobject-representability-morello.c | 10 +- 2 files changed, 167 insertions(+), 80 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp index b6bd8b6ee727..143faf9060a9 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp @@ -34,17 +34,131 @@ using namespace ento; namespace { class SubObjectRepresentabilityChecker - : public Checker> { + : public Checker, check::ASTCodeBody> { BugType BT_1{this, "Field with imprecise subobject bounds", "CHERI portability"}; + BugType BT_2{this, "Address taken of a field with imprecise subobject bounds", + "CHERI portability"}; public: void checkASTDecl(const RecordDecl *R, AnalysisManager& mgr, BugReporter &BR) const; + void checkASTCodeBody(const Decl *D, AnalysisManager &mgr, + BugReporter &BR) const; }; } +namespace { + +std::unique_ptr reportExposedFields(const FieldDecl *D, ASTContext &ASTCtx, BugReporter &BR, + uint64_t Base, uint64_t Top, std::unique_ptr Report) { + const RecordDecl *Parent = D->getParent(); + auto FI = Parent->field_begin(); + while (FI != Parent->field_end() && + ASTCtx.getFieldOffset(*FI) / 8 + + ASTCtx.getTypeSize(FI->getType()) / 8 <= + Base) + FI++; + + bool Before = true; + while (FI != Parent->field_end() && + ASTCtx.getFieldOffset(*FI) / 8 < Top) { + if (*FI != D) { + SmallString<1024> Note; + llvm::raw_svector_ostream OS2(Note); + + uint64_t CurFieldOffset = ASTCtx.getFieldOffset(*FI) / 8; + uint64_t CurFieldSize = ASTCtx.getTypeSize(FI->getType()) / 8; + uint64_t BytesExposed = + Before + ? std::min(CurFieldSize, CurFieldOffset + CurFieldSize - Base) + : std::min(CurFieldSize, Top - CurFieldOffset); + OS2 << BytesExposed << "/" << CurFieldSize << " bytes exposed"; + if (cheri::hasCapability(FI->getType(), ASTCtx)) + OS2 << " (may expose capability!)"; + + PathDiagnosticLocation LN = + PathDiagnosticLocation::createBegin(*FI, BR.getSourceManager()); + Report->addNote(OS2.str(), LN); + } else + Before = false; + FI++; + } + return Report; +} + +// FIXME: CC_MORELLO is not set in cheri_compressed_cap +// FIXME: other targets +using Handler = CompressedCap128; +Handler::cap_t getBoundedCap(uint64_t ParentSize, uint64_t Offset, + uint64_t Size) { + Handler::addr_t InitLength = Handler ::representable_length(ParentSize); + Handler::cap_t MockCap = Handler::make_max_perms_cap(0, 0, InitLength); + bool exact = Handler::setbounds(&MockCap, Offset, Offset + Size); + assert(!exact); + return MockCap; +} + +} // namespace + +std::unique_ptr checkField(const FieldDecl *D, AnalysisManager &mgr, + BugReporter &BR, const BugType &BT) { + QualType T = D->getType(); + + ASTContext &ASTCtx = BR.getContext(); + uint64_t Offset = ASTCtx.getFieldOffset(D) / 8; + if (Offset > 0) { + uint64_t Size = ASTCtx.getTypeSize(T) / 8; + uint64_t ReqAlign = llvm::getMorelloRequiredAlignment(Size); + uint64_t CurAlign = 1 << llvm::countTrailingZeros(Offset); + if (CurAlign < ReqAlign) { + /* Emit warning */ + SmallString<1024> Err; + llvm::raw_svector_ostream OS(Err); + const PrintingPolicy &PP = ASTCtx.getPrintingPolicy(); + OS << "Field '"; + D->getNameForDiagnostic(OS, PP, false); + OS << "' of type '" << T.getAsString(PP) << "'"; + OS << " (size " << Size << ")"; + OS << " requires " << ReqAlign << " byte alignment for precise bounds;"; + OS << " field offset is " << Offset; + OS << " (aligned to " << CurAlign << ");"; + + /* + * Print current bounds + * TODO: use cheri_compressed_cap correctly + * + const RecordDecl *Parent = D->getParent(); + uint64_t ParentSize = ASTCtx.getTypeSize(Parent->getTypeForDecl()) / 8; + auto MockCap = getBoundedCap(ParentSize, Offset, Size); + uint64_t Base = MockCap.base(); + uint64_t Top = MockCap.top(); + OS << " Current bounds: " << Base << "-" << Top; + */ + + // Note that this will fire for every translation unit that uses this + // class. This is suboptimal, but at least scan-build will merge + // duplicate HTML reports. + PathDiagnosticLocation L = + PathDiagnosticLocation::createBegin(D, BR.getSourceManager()); + auto Report = std::make_unique(BT, OS.str(), L); + Report->setDeclWithIssue(D); + Report->addRange(D->getSourceRange()); + + /* + * Add exposed fields as notes + * TODO: use cheri_compressed_cap correctly + Report = reportExposedFields(D, ASTCtx, BR, Base, Top, std::move(Report)); + */ + + return Report; + } + } + + return nullptr; +} + void SubObjectRepresentabilityChecker::checkASTDecl(const RecordDecl *R, AnalysisManager &mgr, BugReporter &BR) const { @@ -61,82 +175,55 @@ void SubObjectRepresentabilityChecker::checkASTDecl(const RecordDecl *R, if (Kind != SrcMgr::C_User) return; */ - + for (FieldDecl *D : R->fields()) { - QualType T = D->getType(); - - ASTContext &ASTCtx = BR.getContext(); - uint64_t Offset = ASTCtx.getFieldOffset(D)/8; - if (Offset > 0) { - uint64_t Size = ASTCtx.getTypeSize(T)/8; - uint64_t ReqAlign = llvm::getMorelloRequiredAlignment(Size); - uint64_t CurAlign = 1 << llvm::countTrailingZeros(Offset); - if (CurAlign < ReqAlign) { - /* Emit warning */ - SmallString<1024> Err; - llvm::raw_svector_ostream OS(Err); - const PrintingPolicy &PP = ASTCtx.getPrintingPolicy(); - OS << "Field '"; - D->getNameForDiagnostic(OS, PP, false); - OS << "' of type '" << T.getAsString(PP) << "'"; - OS << " (size " << Size << ")"; - OS << " requires " << ReqAlign << " byte alignment for precise bounds;"; - OS << " field offset is " << Offset; - OS << " (aligned to " << CurAlign << ");"; - - //FIXME: other targets - using Handler = CompressedCap128; - uint64_t ParentSize = ASTCtx.getTypeSize(R->getTypeForDecl())/8; - Handler::addr_t InitLength = Handler ::representable_length(ParentSize); - Handler::cap_t MockCap = Handler::make_max_perms_cap(0, 0, InitLength); - bool exact = Handler::setbounds(&MockCap, Offset, Offset + Size); - assert(!exact); - auto Base = MockCap.base(); - Handler::addr_t Top = MockCap.top(); - OS << " Current bounds: " << Base << "-" << Top; - - PathDiagnosticLocation L = - PathDiagnosticLocation::createBegin(D, BR.getSourceManager()); - auto Report = - std::make_unique(BT_1, OS.str(), L); - Report->setDeclWithIssue(D); - Report->addRange(D->getSourceRange()); - - - auto FI = R->field_begin(); - while (FI != R->field_end() && - ASTCtx.getFieldOffset(*FI)/8 - + ASTCtx.getTypeSize(FI->getType())/8 <= Base) - FI++; - - bool Before = true; - while (FI != R->field_end() && ASTCtx.getFieldOffset(*FI)/8 < Top) { - if (*FI != D) { - SmallString<1024> Note; - llvm::raw_svector_ostream OS2(Note); - - uint64_t CurFieldOffset = ASTCtx.getFieldOffset(*FI)/8; - uint64_t CurFieldSize = ASTCtx.getTypeSize(FI->getType())/8; - uint64_t BytesExposed = - Before ? std::min(CurFieldSize, - CurFieldOffset + CurFieldSize - Base) - : std::min(CurFieldSize, Top - CurFieldOffset); - OS2 << BytesExposed << "/" << CurFieldSize << " bytes exposed"; - if (cheri::hasCapability(FI->getType(), ASTCtx)) - OS2 << " (may expose capability!)"; - - PathDiagnosticLocation LN = - PathDiagnosticLocation::createBegin(*FI, BR.getSourceManager()); - Report->addNote(OS2.str(), LN); - } else - Before = false; - FI++; - } + auto Report = checkField(D, mgr, BR, BT_1); + if (Report) + BR.emitReport(std::move(Report)); + } +} - // Note that this will fire for every translation unit that uses this - // class. This is suboptimal, but at least scan-build will merge - // duplicate HTML reports. - BR.emitReport(std::move(Report)); +void SubObjectRepresentabilityChecker::checkASTCodeBody(const Decl *D, AnalysisManager &mgr, + BugReporter &BR) const { + using namespace ast_matchers; + + auto Member = memberExpr().bind("member"); + auto Decay = + castExpr(hasCastKind(CK_ArrayToPointerDecay), has(Member)).bind("decay"); + auto Addr = unaryOperator(hasOperatorName("&"), has(Member)).bind("addr"); + + auto PointerSizeCheck = traverse(TK_AsIs, stmt(anyOf(Decay, Addr))); + + auto Matcher = decl(forEachDescendant(PointerSizeCheck)); + + auto Matches = match(Matcher, *D, BR.getContext()); + for (const auto &Match : Matches) { + if (const CastExpr *CE = Match.getNodeAs("decay")) { + if (const MemberExpr *ME = Match.getNodeAs("member")) { + ValueDecl *VD = ME->getMemberDecl(); + if (FieldDecl *FD = dyn_cast(VD)) { + auto Report = checkField(FD, mgr, BR, BT_2); + if (Report) { + PathDiagnosticLocation LN = PathDiagnosticLocation::createBegin( + CE, BR.getSourceManager(), mgr.getAnalysisDeclContext(D)); + Report->addNote("Array to pointer decay", LN); + BR.emitReport(std::move(Report)); + } + } + } + } else if (const UnaryOperator *UO = + Match.getNodeAs("addr")) { + if (const MemberExpr *ME = Match.getNodeAs("member")) { + ValueDecl *VD = ME->getMemberDecl(); + if (FieldDecl *FD = dyn_cast(VD)) { + auto Report = checkField(FD, mgr, BR, BT_2); + if (Report) { + PathDiagnosticLocation LN = PathDiagnosticLocation::createBegin( + UO, BR.getSourceManager(), mgr.getAnalysisDeclContext(D)); + Report->addNote("Address of a field taken", LN); + BR.emitReport(std::move(Report)); + } + } } } } diff --git a/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c b/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c index f34eb2e74537..3740bf1cfdef 100644 --- a/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c +++ b/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c @@ -11,7 +11,7 @@ struct R1 { char a[0x3FFF]; // no warn } f1good; struct { - char c; // expected-note{{}} + char c; char a[0x4000]; // expected-warning{{Field 'a' of type 'char[16384]' (size 16384) requires 8 byte alignment for precise bounds; field offset is 1}} } f2bad; struct { @@ -27,9 +27,9 @@ struct S2 { }; struct R2 { - char x[0x50]; // expected-note{{16/80}} - struct S2 s2; // expected-note{{32/32 bytes exposed (may expose capability!)}} - char c; // expected-note{{1}} + char x[0x50]; + struct S2 s2; + char c; char a[0x8000]; // expected-warning{{Field 'a' of type 'char[32768]'}} - char y[32]; // expected-note{{15/32}} + char y[32]; }; \ No newline at end of file From 9699a8e7d1ae5b391ba50cd18701a3007f1056a6 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 6 May 2024 14:45:29 +0100 Subject: [PATCH 63/77] [CHERI_CSA] SubObjectRepresentability: enable notes with updated cheri-compressed-cap --- .../SubObjectRepresentabilityChecker.cpp | 89 +++++++++++-------- .../subobject-representability-morello.c | 14 +-- 2 files changed, 61 insertions(+), 42 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp index 143faf9060a9..861e105924ca 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp @@ -15,9 +15,9 @@ //===----------------------------------------------------------------------===// #include "CHERIUtils.h" -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/StmtVisitor.h" #include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -25,10 +25,8 @@ #include "llvm/ADT/SmallString.h" #include "llvm/Support/raw_ostream.h" -#include "llvm/Support/Morello.h" #include "llvm/CHERI/cheri-compressed-cap/cheri_compressed_cap.h" - using namespace clang; using namespace ento; @@ -47,12 +45,14 @@ class SubObjectRepresentabilityChecker BugReporter &BR) const; }; -} +} //namespace namespace { -std::unique_ptr reportExposedFields(const FieldDecl *D, ASTContext &ASTCtx, BugReporter &BR, - uint64_t Base, uint64_t Top, std::unique_ptr Report) { +std::unique_ptr +reportExposedFields(const FieldDecl *D, ASTContext &ASTCtx, BugReporter &BR, + uint64_t Base, uint64_t Top, + std::unique_ptr Report) { const RecordDecl *Parent = D->getParent(); auto FI = Parent->field_begin(); while (FI != Parent->field_end() && @@ -88,29 +88,34 @@ std::unique_ptr reportExposedFields(const FieldDecl *D, ASTConte return Report; } -// FIXME: CC_MORELLO is not set in cheri_compressed_cap -// FIXME: other targets -using Handler = CompressedCap128; -Handler::cap_t getBoundedCap(uint64_t ParentSize, uint64_t Offset, - uint64_t Size) { - Handler::addr_t InitLength = Handler ::representable_length(ParentSize); - Handler::cap_t MockCap = Handler::make_max_perms_cap(0, 0, InitLength); - bool exact = Handler::setbounds(&MockCap, Offset, Offset + Size); +template +typename Handler::cap_t getBoundedCap(uint64_t ParentSize, uint64_t Offset, + uint64_t Size) { + typename Handler::addr_t InitLength = + Handler::representable_length(ParentSize); + typename Handler::cap_t MockCap = + Handler::make_max_perms_cap(0, Offset, InitLength); + bool exact = Handler::setbounds(&MockCap, Size); assert(!exact); return MockCap; } -} // namespace +template +uint64_t getRepresentableAlignment(uint64_t Size) { + return ~Handler::representable_mask(Size) + 1; +} -std::unique_ptr checkField(const FieldDecl *D, AnalysisManager &mgr, - BugReporter &BR, const BugType &BT) { +template +std::unique_ptr checkFieldImpl(const FieldDecl *D, + BugReporter &BR, + const BugType &BT) { QualType T = D->getType(); ASTContext &ASTCtx = BR.getContext(); uint64_t Offset = ASTCtx.getFieldOffset(D) / 8; if (Offset > 0) { uint64_t Size = ASTCtx.getTypeSize(T) / 8; - uint64_t ReqAlign = llvm::getMorelloRequiredAlignment(Size); + uint64_t ReqAlign = getRepresentableAlignment(Size); uint64_t CurAlign = 1 << llvm::countTrailingZeros(Offset); if (CurAlign < ReqAlign) { /* Emit warning */ @@ -125,17 +130,13 @@ std::unique_ptr checkField(const FieldDecl *D, AnalysisManager &mgr, OS << " field offset is " << Offset; OS << " (aligned to " << CurAlign << ");"; - /* - * Print current bounds - * TODO: use cheri_compressed_cap correctly - * const RecordDecl *Parent = D->getParent(); uint64_t ParentSize = ASTCtx.getTypeSize(Parent->getTypeForDecl()) / 8; - auto MockCap = getBoundedCap(ParentSize, Offset, Size); + typename Handler::cap_t MockCap = + getBoundedCap(ParentSize, Offset, Size); uint64_t Base = MockCap.base(); uint64_t Top = MockCap.top(); OS << " Current bounds: " << Base << "-" << Top; - */ // Note that this will fire for every translation unit that uses this // class. This is suboptimal, but at least scan-build will merge @@ -146,11 +147,7 @@ std::unique_ptr checkField(const FieldDecl *D, AnalysisManager &mgr, Report->setDeclWithIssue(D); Report->addRange(D->getSourceRange()); - /* - * Add exposed fields as notes - * TODO: use cheri_compressed_cap correctly Report = reportExposedFields(D, ASTCtx, BR, Base, Top, std::move(Report)); - */ return Report; } @@ -159,10 +156,29 @@ std::unique_ptr checkField(const FieldDecl *D, AnalysisManager &mgr, return nullptr; } +std::unique_ptr checkField(const FieldDecl *D, + BugReporter &BR, + const BugType &BT) { + // TODO: other targets + return checkFieldImpl(D, BR, BT); +} + +bool supportedTarget(const ASTContext &C) { + const TargetInfo &TI = C.getTargetInfo(); + return TI.areAllPointersCapabilities() + && TI.getTriple().isAArch64(); // morello +} + +} // namespace + + void SubObjectRepresentabilityChecker::checkASTDecl(const RecordDecl *R, AnalysisManager &mgr, BugReporter &BR) const { - if (!R->isCompleteDefinition()) + if (!supportedTarget(mgr.getASTContext())) + return; + + if (!R->isCompleteDefinition() || R->isDependentType()) return; if (!R->getLocation().isValid()) @@ -177,16 +193,19 @@ void SubObjectRepresentabilityChecker::checkASTDecl(const RecordDecl *R, */ for (FieldDecl *D : R->fields()) { - auto Report = checkField(D, mgr, BR, BT_1); + auto Report = checkField(D, BR, BT_1); if (Report) BR.emitReport(std::move(Report)); } } -void SubObjectRepresentabilityChecker::checkASTCodeBody(const Decl *D, AnalysisManager &mgr, - BugReporter &BR) const { - using namespace ast_matchers; +void SubObjectRepresentabilityChecker::checkASTCodeBody(const Decl *D, + AnalysisManager &mgr, + BugReporter &BR) const { + if (!supportedTarget(mgr.getASTContext())) + return; + using namespace ast_matchers; auto Member = memberExpr().bind("member"); auto Decay = castExpr(hasCastKind(CK_ArrayToPointerDecay), has(Member)).bind("decay"); @@ -202,7 +221,7 @@ void SubObjectRepresentabilityChecker::checkASTCodeBody(const Decl *D, AnalysisM if (const MemberExpr *ME = Match.getNodeAs("member")) { ValueDecl *VD = ME->getMemberDecl(); if (FieldDecl *FD = dyn_cast(VD)) { - auto Report = checkField(FD, mgr, BR, BT_2); + auto Report = checkField(FD, BR, BT_2); if (Report) { PathDiagnosticLocation LN = PathDiagnosticLocation::createBegin( CE, BR.getSourceManager(), mgr.getAnalysisDeclContext(D)); @@ -216,7 +235,7 @@ void SubObjectRepresentabilityChecker::checkASTCodeBody(const Decl *D, AnalysisM if (const MemberExpr *ME = Match.getNodeAs("member")) { ValueDecl *VD = ME->getMemberDecl(); if (FieldDecl *FD = dyn_cast(VD)) { - auto Report = checkField(FD, mgr, BR, BT_2); + auto Report = checkField(FD, BR, BT_2); if (Report) { PathDiagnosticLocation LN = PathDiagnosticLocation::createBegin( UO, BR.getSourceManager(), mgr.getAnalysisDeclContext(D)); diff --git a/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c b/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c index 3740bf1cfdef..71b7f53eef05 100644 --- a/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c +++ b/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c @@ -11,7 +11,7 @@ struct R1 { char a[0x3FFF]; // no warn } f1good; struct { - char c; + char c; // expected-note{{}} char a[0x4000]; // expected-warning{{Field 'a' of type 'char[16384]' (size 16384) requires 8 byte alignment for precise bounds; field offset is 1}} } f2bad; struct { @@ -27,9 +27,9 @@ struct S2 { }; struct R2 { - char x[0x50]; - struct S2 s2; - char c; - char a[0x8000]; // expected-warning{{Field 'a' of type 'char[32768]'}} - char y[32]; -}; \ No newline at end of file + char x[0x50]; // expected-note{{16/80}} + struct S2 s2; // expected-note{{32/32 bytes exposed (may expose capability!)}} + char c; // expected-note{{1}} + char a[0x20000]; // expected-warning{{Field 'a' of type 'char[131072]' (size 131072) requires 64 byte alignment for precise bounds; field offset is 113 (aligned to 1); Current bounds: 64-131200}} + char y[32]; // expected-note{{15/32}} +}; From 3a27311f11e2a2035727bce491af5011d6b65073 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 7 May 2024 12:33:09 +0100 Subject: [PATCH 64/77] [CHERI_CSA] SubObjectRepresentability: move alpha.cheri -> cheri --- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td | 8 ++++---- .../Checkers/CHERI/subobject-representability-morello.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index ac9fbcce4d57..daccc872ab33 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1783,12 +1783,12 @@ def PointerSizeAssumptionsChecker : Checker<"PointerSizeAssumptions">, HelpText<"Detect hardcoded expectations on pointer sizes">, Documentation; +def SubObjectRepresentabilityChecker : Checker<"SubObjectRepresentability">, + HelpText<"Check for record fields with unrepresentable subobject bounds">, + Documentation; + } // end cheri let ParentPackage = CHERIAlpha in { -def SubObjectRepresentabilityChecker : Checker<"SubObjectRepresentability">, - HelpText<"TODO: help">, - Documentation; - } // end alpha.cheri diff --git a/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c b/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c index 71b7f53eef05..4b8c6aebb3f4 100644 --- a/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c +++ b/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c @@ -2,7 +2,7 @@ // XFAIL: * // RUN: %clang_cc1 -triple aarch64-none-elf -target-feature +morello -target-feature +c64 -target-abi purecap \ -// RUN: -analyze -analyzer-checker=core,alpha.cheri.SubObjectRepresentability -verify %s +// RUN: -analyze -analyzer-checker=core,cheri.SubObjectRepresentability -verify %s struct R1 { struct { From 40e900ea25bb08f004489e4018f0eb1d3bdb4a35 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 29 Mar 2024 17:35:26 +0000 Subject: [PATCH 65/77] [CHERI_CSA] New cheri.Allocation checker --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../Checkers/CHERI/AllocationChecker.cpp | 230 ++++++++++++++++++ .../Checkers/CHERI/CHERIUtils.cpp | 11 + .../Checkers/CHERI/CHERIUtils.h | 3 + .../CHERI/ProvenanceSourceChecker.cpp | 14 -- .../StaticAnalyzer/Checkers/CMakeLists.txt | 3 +- .../test/Analysis/Checkers/CHERI/allocation.c | 43 ++++ 7 files changed, 293 insertions(+), 15 deletions(-) create mode 100644 clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp create mode 100644 clang/test/Analysis/Checkers/CHERI/allocation.c diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index daccc872ab33..81c40a689bd6 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1791,4 +1791,8 @@ def SubObjectRepresentabilityChecker : Checker<"SubObjectRepresentability">, let ParentPackage = CHERIAlpha in { +def AllocationChecker : Checker<"Allocation">, + HelpText<"Suggest narrowing bounds for escaping suballocation capabilities">, + Documentation; + } // end alpha.cheri diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp new file mode 100644 index 000000000000..75211d5e3c08 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp @@ -0,0 +1,230 @@ +//===-- AllocationChecker.cpp - Allocation Checker -*- 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 checker that detects unrelated objects being allocated as +// adjacent suballocations of the bigger memory allocation. It reports +// situations when an address of such object escapes the function and +// suggests narrowing the bounds of the escaping capability, so it covers only +// the current suballocation, following the principle of least privilege. +// +//===----------------------------------------------------------------------===// +#include "CHERIUtils.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + + +using namespace clang; +using namespace ento; +using namespace cheri; + +namespace { + +class AllocationChecker : public Checker> { + BugType BT_1{this, "Allocation partitioning", "CHERI portability"}; + + class AllocPartitionBugVisitor : public BugReporterVisitor { + public: + AllocPartitionBugVisitor(const MemRegion *R) : Reg(R) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Reg); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; + + private: + const MemRegion *Reg; + }; + +public: + void checkPostStmt(const CastExpr *CE, CheckerContext &C) const; + +private: + ExplodedNode *emitAllocationPartitionWarning(const CastExpr *CE, + CheckerContext &C, + const MemRegion *R) const; +}; + +} // namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(AllocMap, const MemRegion *, QualType) +REGISTER_MAP_WITH_PROGRAMSTATE(ShiftMap, const MemRegion *, const MemRegion *) + +namespace { +std::pair getAllocationStart(const MemRegion *R, + CheckerContext &C, + bool ZeroShift = true) { + if (const ElementRegion *ER = R->getAs()) { + const MemRegion *Base = ER->getSuperRegion(); + return getAllocationStart(Base, C, ER->getIndex().isZeroConstant()); + } + if (auto OrigR = C.getState()->get(R)) { + return std::make_pair(*OrigR, false); + } + return std::make_pair(R, ZeroShift); +} + +bool isAllocation(const MemRegion *R) { + if (R->getAs()) + return true; + if (const TypedValueRegion *TR = R->getAs()) { + return TR->getValueType()->isArrayType(); + } + return false; +} + +bool relatedTypes(const Type *Ty1, const Type *Ty2) { + if (Ty1 == Ty2) + return true; + if (Ty1->isArrayType()) + return relatedTypes(Ty1->getArrayElementTypeNoTypeQual(), Ty2); + return false; +} + +bool isGenPtrType(QualType Ty) { + return Ty->isVoidPointerType() || + ((Ty->isPointerType() || Ty->isArrayType()) && + Ty->getPointeeOrArrayElementType()->isCharType()); +} + +Optional getPrevType(ProgramStateRef State, const MemRegion *R) { + if (const QualType *PrevTy = State->get(R)) + return *PrevTy; + if (const TypedValueRegion *TR = R->getAs()) { + QualType Ty = TR->getValueType(); + if ((Ty->isPointerType() || Ty->isArrayType()) && !isGenPtrType(Ty)) + return Ty; + } + return None; +} + +} // namespace + +ExplodedNode *AllocationChecker::emitAllocationPartitionWarning( + const CastExpr *CE, CheckerContext &C, const MemRegion *MR) const { + if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << "Allocation partition: "; + describeCast(OS, CE, C.getASTContext().getLangOpts()); + auto R = std::make_unique( + BT_1, OS.str(), ErrNode); + R->addVisitor(std::make_unique(MR)); + C.emitReport(std::move(R)); + return ErrNode; + } + return nullptr; +} + +void AllocationChecker::checkPostStmt(const CastExpr *CE, + CheckerContext &C) const { + if (CE->getCastKind() != CK_BitCast) + return; + SVal SrcVal = C.getSVal(CE->getSubExpr()); + const MemRegion *MR = SrcVal.getAsRegion(); + if (!MR) + return; + if (MR->getMemorySpace()->getKind() == MemSpaceRegion::CodeSpaceRegionKind) + return; + + ProgramStateRef State = C.getState(); + bool Updated = false; + + std::pair StartPair = getAllocationStart(MR, C); + const MemRegion *SR = StartPair.first; + if (!isAllocation(SR)) + return; + bool ZeroShift = StartPair.second; + + SVal DstVal = C.getSVal(CE); + const MemRegion *DMR = DstVal.getAsRegion(); + if (MR->getAs() && (!DMR || !DMR->getAs())) { + if (DstVal.isUnknown()) { + const LocationContext *LCtx = C.getLocationContext(); + DstVal = C.getSValBuilder().conjureSymbolVal( + nullptr, CE, LCtx, CE->getType(), C.blockCount()); + State = State->BindExpr(CE, LCtx, DstVal); + DMR = DstVal.getAsRegion(); + } + if (DMR) { + State = State->set(DMR, SR); + Updated = true; + } + } + + QualType DstTy = CE->getType().getUnqualifiedType(); + if (!DstTy->isPointerType()) + return; + if (DstTy->isVoidPointerType() || DstTy->getPointeeType()->isCharType()) + return; + + Optional PrevTy = getPrevType(State, SR); + if (PrevTy.hasValue()) { + if (SR != MR && !ZeroShift) { + const Type *Ty1 = PrevTy.getValue() + ->getPointeeOrArrayElementType() + ->getUnqualifiedDesugaredType(); + const Type *Ty2 = DstTy->getPointeeType()->getUnqualifiedDesugaredType(); + if (!relatedTypes(Ty1, Ty2)) { + emitAllocationPartitionWarning(CE, C, SR); + return; + } // else OK + } // else ??? (ignore for now) + } else { + State = State->set(SR, DstTy); + Updated = true; + } + + if (Updated) + C.addTransition(State); +} + +PathDiagnosticPieceRef AllocationChecker::AllocPartitionBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { + const Stmt *S = N->getStmtForDiagnostics(); + if (!S) + return nullptr; + + const CastExpr *CE = dyn_cast(S); + if (!CE || CE->getCastKind() != CK_BitCast) + return nullptr; + + if (Reg != N->getSVal(CE->getSubExpr()).getAsRegion()) + return nullptr; + + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << "Previous partition: "; + describeCast(OS, CE, BRC.getASTContext().getLangOpts()); + + // Generate the extra diagnostic. + PathDiagnosticLocation const Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return std::make_shared(Pos, OS.str(), true); +} + +//===----------------------------------------------------------------------===// +// Checker registration. +//===----------------------------------------------------------------------===// + +void ento::registerAllocationChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterAllocationChecker(const CheckerManager &Mgr) { + return true; +} \ No newline at end of file diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp index 57594e9e819b..6c099da7e806 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp @@ -52,6 +52,17 @@ bool hasCapability(const QualType OrigTy, ASTContext &Ctx) { return false; } +void describeCast(raw_ostream &OS, const CastExpr *CE, + const LangOptions &LangOpts) { + OS << (dyn_cast(CE) ? "implicit" : "explicit"); + OS << " cast from '"; + CE->getSubExpr()->getType().print(OS, PrintingPolicy(LangOpts)); + OS << "' to '"; + CE->getType().print(OS, PrintingPolicy(LangOpts)); + OS << "'"; +} + + } // end of namespace: cheri } // end of namespace: ento } // end of namespace: clang \ No newline at end of file diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h index 372a2fcfc447..82a0eb06e44d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h @@ -27,6 +27,9 @@ bool isGenericPointerType(const QualType T, bool AcceptCharPtr = true); bool hasCapability(const QualType OrigTy, ASTContext &Ctx); +void describeCast(raw_ostream &OS, const CastExpr *CE, + const LangOptions &LangOpts); + } // end of namespace: cheri } // end of namespace: ento } // end of namespace: clang diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index 86eddf53073a..79dd895eea2b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -537,20 +537,6 @@ void ProvenanceSourceChecker::checkDeadSymbols(SymbolReaper &SymReaper, C.addTransition(State); } -namespace { - -static void describeCast(raw_ostream &OS, const CastExpr *CE, - const LangOptions &LangOpts) { - OS << (dyn_cast(CE) ? "implicit" : "explicit"); - OS << " cast from '"; - CE->getSubExpr()->getType().print(OS, PrintingPolicy(LangOpts)); - OS << "' to '"; - CE->getType().print(OS, PrintingPolicy(LangOpts)); - OS << "'"; -} - -} // namespace - PathDiagnosticPieceRef ProvenanceSourceChecker::InvalidCapBugVisitor::VisitNode( const ExplodedNode *N, BugReporterContext &BRC, diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index aeadda9ff367..ebec5d6d493a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -24,10 +24,11 @@ add_clang_library(clangStaticAnalyzerCheckers CheckSecuritySyntaxOnly.cpp CheckSizeofPointer.cpp CheckerDocumentation.cpp + CHERI/AllocationChecker.cpp + CHERI/CapabilityCopyChecker.cpp CHERI/CHERIUtils.cpp CHERI/PointerSizeAssumptionsChecker.cpp CHERI/ProvenanceSourceChecker.cpp - CHERI/CapabilityCopyChecker.cpp CHERI/SubObjectRepresentabilityChecker.cpp ChrootChecker.cpp CloneChecker.cpp diff --git a/clang/test/Analysis/Checkers/CHERI/allocation.c b/clang/test/Analysis/Checkers/CHERI/allocation.c new file mode 100644 index 000000000000..7751dfe4de64 --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/allocation.c @@ -0,0 +1,43 @@ +// RUN: %cheri_purecap_cc1 -analyze -verify %s \ +// RUN: -analyzer-checker=core,unix,alpha.cheri.Allocation + +typedef __typeof__(sizeof(int)) size_t; +extern void * malloc(size_t); + + +struct S1 { + int *a[3]; + int *d[1]; +}; + +struct S2 { + int x[3]; + int *px; +}; + +struct S2 * test_1(int n1, int n2) { + struct S1 *p1 = malloc(sizeof(struct S1)*n1 + sizeof(struct S2)*n2); + struct S2 *p2 = (struct S2 *)(p1+n1); // expected-warning{{Allocation partition}} + return p2; +} + +double buf[100] __attribute__((aligned(_Alignof(void*)))); +struct S2 * test_2(int n1) { + struct S1 *p1 = (struct S1 *)buf; // ? + struct S2 *p2 = (struct S2 *)(p1+n1); // expected-warning{{Allocation partition}} + return p2; +} + +int * test_3(int n1, int n2) { + void *p1 = malloc(sizeof(struct S1)*n1 + sizeof(struct S2)*n2); + struct S2 *p2 = (struct S2 *)(p1+n1); + int *p3 = (int*)(p2 + n2); // expected-warning{{Allocation partition}} + return p3; +} + +void array(int i, int j) { + int a[100][200]; + int (*p1)[200] = &a[i]; + int *p2 = p1[j]; // no warn + *p2 = 42; +} From c946dafbfaeb6702e56ef8c19f558a6b21ee52cd Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 18 Apr 2024 14:20:18 +0100 Subject: [PATCH 66/77] [CHERI_CSA] AllocationChecker: move static and heap allocation to new BugType --- .../StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp index 75211d5e3c08..cbc8300bcf36 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp @@ -29,7 +29,9 @@ using namespace cheri; namespace { class AllocationChecker : public Checker> { - BugType BT_1{this, "Allocation partitioning", "CHERI portability"}; + BugType BT_Default{this, "Allocation partitioning", "CHERI portability"}; + BugType BT_KnownReg{this, "Heap or static allocation partitioning", + "CHERI portability"}; class AllocPartitionBugVisitor : public BugReporterVisitor { public: @@ -120,8 +122,10 @@ ExplodedNode *AllocationChecker::emitAllocationPartitionWarning( llvm::raw_svector_ostream OS(Buf); OS << "Allocation partition: "; describeCast(OS, CE, C.getASTContext().getLangOpts()); + const MemSpaceRegion *MemSpace = MR->getMemorySpace(); + bool KnownReg = isa(MemSpace); auto R = std::make_unique( - BT_1, OS.str(), ErrNode); + KnownReg ? BT_KnownReg : BT_Default, OS.str(), ErrNode); R->addVisitor(std::make_unique(MR)); C.emitReport(std::move(R)); return ErrNode; From f9a4b194b7cd05b066b26aac4d3f24f5b64b2595 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 18 Apr 2024 14:47:05 +0100 Subject: [PATCH 67/77] [CHERI_CSA] AllocationChecker: suppress for ptr to first field --- .../Checkers/CHERI/AllocationChecker.cpp | 25 +++++++++++++------ .../test/Analysis/Checkers/CHERI/allocation.c | 20 +++++++++++---- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp index cbc8300bcf36..681f0bbb54d7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp @@ -93,13 +93,24 @@ bool relatedTypes(const Type *Ty1, const Type *Ty2) { return true; if (Ty1->isArrayType()) return relatedTypes(Ty1->getArrayElementTypeNoTypeQual(), Ty2); + if (Ty1->isRecordType()) { + if (RecordDecl *RD = Ty1->getAs()->getAsRecordDecl()) { + const RecordDecl::field_iterator &FirstField = RD->field_begin(); + if (FirstField != RD->field_end()) { + const Type *FFTy = FirstField->getType()->getUnqualifiedDesugaredType(); + return relatedTypes(FFTy, Ty2); + } + } + } return false; } -bool isGenPtrType(QualType Ty) { - return Ty->isVoidPointerType() || - ((Ty->isPointerType() || Ty->isArrayType()) && - Ty->getPointeeOrArrayElementType()->isCharType()); +bool reportForType(QualType Ty) { + if (Ty->isVoidPointerType()) + return false; + if (Ty->isPointerType() || Ty->isArrayType()) + return !Ty->getPointeeOrArrayElementType()->isCharType(); + return false; } Optional getPrevType(ProgramStateRef State, const MemRegion *R) { @@ -107,7 +118,7 @@ Optional getPrevType(ProgramStateRef State, const MemRegion *R) { return *PrevTy; if (const TypedValueRegion *TR = R->getAs()) { QualType Ty = TR->getValueType(); - if ((Ty->isPointerType() || Ty->isArrayType()) && !isGenPtrType(Ty)) + if (reportForType(Ty)) return Ty; } return None; @@ -170,9 +181,7 @@ void AllocationChecker::checkPostStmt(const CastExpr *CE, } QualType DstTy = CE->getType().getUnqualifiedType(); - if (!DstTy->isPointerType()) - return; - if (DstTy->isVoidPointerType() || DstTy->getPointeeType()->isCharType()) + if (!reportForType(DstTy)) return; Optional PrevTy = getPrevType(State, SR); diff --git a/clang/test/Analysis/Checkers/CHERI/allocation.c b/clang/test/Analysis/Checkers/CHERI/allocation.c index 7751dfe4de64..3863ee459c0f 100644 --- a/clang/test/Analysis/Checkers/CHERI/allocation.c +++ b/clang/test/Analysis/Checkers/CHERI/allocation.c @@ -28,11 +28,10 @@ struct S2 * test_2(int n1) { return p2; } -int * test_3(int n1, int n2) { - void *p1 = malloc(sizeof(struct S1)*n1 + sizeof(struct S2)*n2); - struct S2 *p2 = (struct S2 *)(p1+n1); - int *p3 = (int*)(p2 + n2); // expected-warning{{Allocation partition}} - return p3; +struct S2 * test_3(int n1, int n2) { + struct S1 *p1 = malloc(sizeof(struct S1)*n1 + sizeof(struct S2)*n2); + struct S2 *p2 = (struct S2 *)(p1+n1); // expected-warning{{Allocation partition}} + return p2; } void array(int i, int j) { @@ -41,3 +40,14 @@ void array(int i, int j) { int *p2 = p1[j]; // no warn *p2 = 42; } + +struct S3 { + struct S2 s2; + int y; +}; + +struct S2 * first_field(void *p, int n1) { + struct S3 *p3 = p; + struct S2 *p2 = (struct S2 *)(p3+n1); // no warn + return p2; +} From ef612bb807f56033ba3846054a7ae1126b4c8c29 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Tue, 28 May 2024 17:19:59 +0100 Subject: [PATCH 68/77] [CHERI_CSA] CHERIUtils: Print aka type in messages --- .../Checkers/CHERI/CHERIUtils.cpp | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp index 6c099da7e806..8e32a5a93e17 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp @@ -52,14 +52,29 @@ bool hasCapability(const QualType OrigTy, ASTContext &Ctx) { return false; } +namespace { +void printType(raw_ostream &OS, const QualType &Ty, + const PrintingPolicy &PP) { + OS << "'"; + Ty.print(OS, PP); + OS << "'"; + const QualType &CanTy = Ty.getCanonicalType(); + if (CanTy != Ty) { + OS << " (aka '"; + CanTy.print(OS, PP); + OS << "')"; + } +} +} // namespace + void describeCast(raw_ostream &OS, const CastExpr *CE, const LangOptions &LangOpts) { + const PrintingPolicy &PP = PrintingPolicy(LangOpts); OS << (dyn_cast(CE) ? "implicit" : "explicit"); - OS << " cast from '"; - CE->getSubExpr()->getType().print(OS, PrintingPolicy(LangOpts)); - OS << "' to '"; - CE->getType().print(OS, PrintingPolicy(LangOpts)); - OS << "'"; + OS << " cast from "; + printType(OS, CE->getSubExpr()->getType(), PP); + OS << " to "; + printType(OS, CE->getType(), PP); } From bbf8fdcd04434a522f29d3d426793d5368137d83 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 6 May 2024 14:49:43 +0100 Subject: [PATCH 69/77] [CHERI_CSA] AllocationChecker: suppress for flexible array --- .../Checkers/CHERI/AllocationChecker.cpp | 37 ++++++++++++++++++- .../test/Analysis/Checkers/CHERI/allocation.c | 11 ++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp index 681f0bbb54d7..f4fce8f8974a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp @@ -105,11 +105,44 @@ bool relatedTypes(const Type *Ty1, const Type *Ty2) { return false; } +bool hasFlexibleArrayMember(const Type *PTy) { + const RecordType *RTy = dyn_cast(PTy); + if (!RTy) + return false; + + RecordDecl *RD = RTy->getDecl(); + if (RD->hasFlexibleArrayMember()) + return true; + + // check last field + FieldDecl *LastField = nullptr; + for (auto i = RD->field_begin(), end = RD->field_end(); i != end; ++i) + LastField = *i; + if (!LastField) + return false; + + QualType FieldTy = LastField->getType(); + if (FieldTy->isVariableArrayType() || FieldTy->isIncompleteArrayType()) + return true; + + if (const ConstantArrayType *CAT = + dyn_cast(FieldTy.getTypePtr())) { + return CAT->getSize() == 0 || CAT->getSize() == 1; + } + return false; +} + bool reportForType(QualType Ty) { if (Ty->isVoidPointerType()) return false; - if (Ty->isPointerType() || Ty->isArrayType()) - return !Ty->getPointeeOrArrayElementType()->isCharType(); + if (Ty->isPointerType() || Ty->isArrayType()) { + const Type *PTy = Ty->getPointeeOrArrayElementType(); + if (PTy->isCharType()) + return false; + if (hasFlexibleArrayMember(PTy)) + return false; + return true; + } return false; } diff --git a/clang/test/Analysis/Checkers/CHERI/allocation.c b/clang/test/Analysis/Checkers/CHERI/allocation.c index 3863ee459c0f..9a612177f82a 100644 --- a/clang/test/Analysis/Checkers/CHERI/allocation.c +++ b/clang/test/Analysis/Checkers/CHERI/allocation.c @@ -51,3 +51,14 @@ struct S2 * first_field(void *p, int n1) { struct S2 *p2 = (struct S2 *)(p3+n1); // no warn return p2; } + +struct S4 { + long len; + int buf[]; +}; + +int* flex_array(int len) { + struct S4 *p = malloc(sizeof(struct S4) + len*sizeof(int)); + int *pB = (int*)(p + 1); // no warn + return pB; +} From 5450663611391f6ab656f5629f55f9d550622865 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Mon, 20 May 2024 15:20:00 +0100 Subject: [PATCH 70/77] [CHERI_CSA] AllocationChecker: rework --- .../Checkers/CHERI/AllocationChecker.cpp | 235 ++++++++++++++---- .../Checkers/CHERI/CHERIUtils.cpp | 23 +- .../Checkers/CHERI/CHERIUtils.h | 2 + .../Checkers/PointerAlignmentChecker.cpp | 16 +- .../test/Analysis/Checkers/CHERI/allocation.c | 52 ++-- 5 files changed, 244 insertions(+), 84 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp index f4fce8f8974a..50b560fff075 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp @@ -14,33 +14,52 @@ // //===----------------------------------------------------------------------===// #include "CHERIUtils.h" -#include "clang/StaticAnalyzer/Core/Checker.h" -#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #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/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" using namespace clang; using namespace ento; using namespace cheri; -namespace { -class AllocationChecker : public Checker> { +struct EscapeInfo { + PointerEscapeKind Kind; + EscapeInfo(PointerEscapeKind K) : Kind(K) {}; + bool operator==(const EscapeInfo &X) const { + return Kind == X.Kind; + } + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(Kind); + } +}; +using EscapePair = std::pair; + +namespace { +class AllocationChecker : public Checker, + check::PreCall, + check::Bind, + check::EndFunction> { BugType BT_Default{this, "Allocation partitioning", "CHERI portability"}; BugType BT_KnownReg{this, "Heap or static allocation partitioning", "CHERI portability"}; class AllocPartitionBugVisitor : public BugReporterVisitor { public: - AllocPartitionBugVisitor(const MemRegion *R) : Reg(R) {} + AllocPartitionBugVisitor(const MemRegion *P, const MemRegion *A) + : PrevAlloc(P), SubAlloc(A) {} void Profile(llvm::FoldingSetNodeID &ID) const override { static int X = 0; ID.AddPointer(&X); - ID.AddPointer(Reg); + ID.AddPointer(PrevAlloc); + ID.AddPointer(SubAlloc); + } PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, @@ -48,32 +67,41 @@ class AllocationChecker : public Checker> { PathSensitiveBugReport &BR) override; private: - const MemRegion *Reg; + const MemRegion *PrevAlloc; + const MemRegion *SubAlloc; + bool PrevReported = false; }; public: void checkPostStmt(const CastExpr *CE, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; + void checkEndFunction(const ReturnStmt *RS, CheckerContext &Ctx) const; private: - ExplodedNode *emitAllocationPartitionWarning(const CastExpr *CE, - CheckerContext &C, - const MemRegion *R) const; + ExplodedNode *emitAllocationPartitionWarning(CheckerContext &C, + const MemRegion *MR, + SourceRange SR, + StringRef Msg) const; }; } // namespace REGISTER_MAP_WITH_PROGRAMSTATE(AllocMap, const MemRegion *, QualType) REGISTER_MAP_WITH_PROGRAMSTATE(ShiftMap, const MemRegion *, const MemRegion *) +REGISTER_SET_WITH_PROGRAMSTATE(SuballocationSet, const MemRegion *) namespace { -std::pair getAllocationStart(const MemRegion *R, - CheckerContext &C, +std::pair getAllocationStart(const ASTContext &ASTCtx, + const MemRegion *R, + ProgramStateRef State, bool ZeroShift = true) { if (const ElementRegion *ER = R->getAs()) { const MemRegion *Base = ER->getSuperRegion(); - return getAllocationStart(Base, C, ER->getIndex().isZeroConstant()); + return getAllocationStart(ASTCtx, Base, State, + ZeroShift && ER->getIndex().isZeroConstant()); } - if (auto OrigR = C.getState()->get(R)) { + if (const auto *OrigR = State->get(R)) { return std::make_pair(*OrigR, false); } return std::make_pair(R, ZeroShift); @@ -88,17 +116,22 @@ bool isAllocation(const MemRegion *R) { return false; } -bool relatedTypes(const Type *Ty1, const Type *Ty2) { +bool relatedTypes(const ASTContext &ASTCtx, const Type *Ty1, const Type *Ty2) { if (Ty1 == Ty2) return true; + if (Ty1->isIntegerType()) { + if (Ty2->isIntegerType()) + return ASTCtx.getTypeSize(Ty1) == ASTCtx.getTypeSize(Ty2); + return false; + } if (Ty1->isArrayType()) - return relatedTypes(Ty1->getArrayElementTypeNoTypeQual(), Ty2); + return relatedTypes(ASTCtx, Ty1->getArrayElementTypeNoTypeQual(), Ty2); if (Ty1->isRecordType()) { if (RecordDecl *RD = Ty1->getAs()->getAsRecordDecl()) { const RecordDecl::field_iterator &FirstField = RD->field_begin(); if (FirstField != RD->field_end()) { const Type *FFTy = FirstField->getType()->getUnqualifiedDesugaredType(); - return relatedTypes(FFTy, Ty2); + return relatedTypes(ASTCtx, FFTy, Ty2); } } } @@ -137,8 +170,11 @@ bool reportForType(QualType Ty) { return false; if (Ty->isPointerType() || Ty->isArrayType()) { const Type *PTy = Ty->getPointeeOrArrayElementType(); + PTy = PTy->getUnqualifiedDesugaredType(); if (PTy->isCharType()) return false; + if (PTy->isPointerType()) + return false; if (hasFlexibleArrayMember(PTy)) return false; return true; @@ -160,17 +196,24 @@ Optional getPrevType(ProgramStateRef State, const MemRegion *R) { } // namespace ExplodedNode *AllocationChecker::emitAllocationPartitionWarning( - const CastExpr *CE, CheckerContext &C, const MemRegion *MR) const { + CheckerContext &C, const MemRegion *MR, SourceRange SR, + StringRef Msg = "") const { if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { - SmallString<256> Buf; - llvm::raw_svector_ostream OS(Buf); - OS << "Allocation partition: "; - describeCast(OS, CE, C.getASTContext().getLangOpts()); - const MemSpaceRegion *MemSpace = MR->getMemorySpace(); - bool KnownReg = isa(MemSpace); - auto R = std::make_unique( - KnownReg ? BT_KnownReg : BT_Default, OS.str(), ErrNode); - R->addVisitor(std::make_unique(MR)); + auto R = std::make_unique(BT_Default, Msg, ErrNode); + R->addRange(SR); + R->markInteresting(MR); + + const MemRegion *PrevAlloc = + getAllocationStart(C.getASTContext(), MR, C.getState()).first; + R->addVisitor(std::make_unique( + PrevAlloc == MR ? nullptr : PrevAlloc, MR)); + + if (const DeclRegion *PrevDecl = getAllocationDecl(PrevAlloc)) { + auto DeclLoc = PathDiagnosticLocation::create(PrevDecl->getDecl(), + C.getSourceManager()); + R->addNote("Original allocation", DeclLoc); + } + C.emitReport(std::move(R)); return ErrNode; } @@ -185,13 +228,16 @@ void AllocationChecker::checkPostStmt(const CastExpr *CE, const MemRegion *MR = SrcVal.getAsRegion(); if (!MR) return; - if (MR->getMemorySpace()->getKind() == MemSpaceRegion::CodeSpaceRegionKind) + const MemSpaceRegion *MemSpace = MR->getMemorySpace(); + if (!isa(MemSpace)) return; + const ASTContext &ASTCtx = C.getASTContext(); ProgramStateRef State = C.getState(); bool Updated = false; - std::pair StartPair = getAllocationStart(MR, C); + std::pair StartPair = + getAllocationStart(ASTCtx, MR, State); const MemRegion *SR = StartPair.first; if (!isAllocation(SR)) return; @@ -224,9 +270,11 @@ void AllocationChecker::checkPostStmt(const CastExpr *CE, ->getPointeeOrArrayElementType() ->getUnqualifiedDesugaredType(); const Type *Ty2 = DstTy->getPointeeType()->getUnqualifiedDesugaredType(); - if (!relatedTypes(Ty1, Ty2)) { - emitAllocationPartitionWarning(CE, C, SR); - return; + if (!relatedTypes(ASTCtx, Ty1, Ty2)) { + State = State->add(SR); + if (DMR) + State = State->add(DMR); + Updated = true; } // else OK } // else ??? (ignore for now) } else { @@ -238,6 +286,88 @@ void AllocationChecker::checkPostStmt(const CastExpr *CE, C.addTransition(State); } +void AllocationChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + ExplodedNode *N = nullptr; + bool Updated = false; + for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { + const Expr *ArgExpr = Call.getArgExpr(Arg); + if (const MemRegion *MR = C.getSVal(ArgExpr).getAsRegion()) { + if (State->contains(MR)) { + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << "Pointer to suballocation passed to function as " << (Arg+1); + if (Arg + 1 < 11) { + switch (Arg+1) { + case 1: OS << "st"; break; + case 2: OS << "nd"; break; + case 3: OS << "rd"; break; + default: OS << "th"; break; + } + } + + OS << " argument"; + OS << " (consider narrowing the bounds for suballocation)"; + N = emitAllocationPartitionWarning( + C, MR, ArgExpr->getSourceRange(), + OS.str()); + if (N) { + State = State->remove(MR); + Updated = true; + } + } + } + } + if (Updated) + C.addTransition(State, N ? N : C.getPredecessor()); +} + +void AllocationChecker::checkBind(SVal L, SVal V, const Stmt *S, + CheckerContext &C) const { + const MemRegion *Dst = L.getAsRegion(); + if (!Dst || !isa(Dst)) + return; + + ProgramStateRef State = C.getState(); + const MemRegion *Val = V.getAsRegion(); + if (Val && State->contains(Val)) { + ExplodedNode *N = emitAllocationPartitionWarning( + C, Val, S->getSourceRange(), + "Pointer to suballocation escaped on assign" + " (consider narrowing the bounds for suballocation)"); + if (N) { + State = State->remove(Val); + C.addTransition(State, N); + } + return; + } +} + +void AllocationChecker::checkEndFunction(const ReturnStmt *RS, + CheckerContext &C) const { + if (!RS) + return; + const Expr *RV = RS->getRetValue(); + if (!RV) + return; + + llvm::SmallVector Escaped; + if (const MemRegion *MR = C.getSVal(RV).getAsRegion()) { + if (C.getState()->contains(MR)) { + ExplodedNode *N = emitAllocationPartitionWarning( + C, MR, RS->getSourceRange(), + "Pointer to suballocation returned from function" + " (consider narrowing the bounds for suballocation)"); + if (N) { + ProgramStateRef State = N->getState()->remove(MR); + C.addTransition(State, N); + } + return; + } + } +} + PathDiagnosticPieceRef AllocationChecker::AllocPartitionBugVisitor::VisitNode( const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) { @@ -245,22 +375,33 @@ PathDiagnosticPieceRef AllocationChecker::AllocPartitionBugVisitor::VisitNode( if (!S) return nullptr; - const CastExpr *CE = dyn_cast(S); - if (!CE || CE->getCastKind() != CK_BitCast) - return nullptr; - - if (Reg != N->getSVal(CE->getSubExpr()).getAsRegion()) - return nullptr; + if (const CastExpr *CE = dyn_cast(S)) { + if (CE->getCastKind() != CK_BitCast) + return nullptr; - SmallString<256> Buf; - llvm::raw_svector_ostream OS(Buf); - OS << "Previous partition: "; - describeCast(OS, CE, BRC.getASTContext().getLangOpts()); + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); - // Generate the extra diagnostic. - PathDiagnosticLocation const Pos(S, BRC.getSourceManager(), - N->getLocationContext()); - return std::make_shared(Pos, OS.str(), true); + if (!PrevReported && PrevAlloc && + PrevAlloc == N->getSVal(CE->getSubExpr()).getAsRegion()) { + OS << "Previous partition: "; + PrevReported = true; + } else if (SubAlloc == N->getSVal(CE).getAsRegion() && + N->getState()->contains(SubAlloc) && + !N->getFirstPred()->getState()->contains( + SubAlloc)) { + OS << "Allocation partition: "; + } else + return nullptr; + + describeCast(OS, CE, BRC.getASTContext().getLangOpts()); + PathDiagnosticLocation const Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + auto Ev = std::make_shared(Pos, OS.str(), true); + Ev->setPrunable(false); + return Ev; + } + return nullptr; } //===----------------------------------------------------------------------===// diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp index 8e32a5a93e17..4add39140f4b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp @@ -53,18 +53,17 @@ bool hasCapability(const QualType OrigTy, ASTContext &Ctx) { } namespace { + void printType(raw_ostream &OS, const QualType &Ty, const PrintingPolicy &PP) { - OS << "'"; - Ty.print(OS, PP); - OS << "'"; - const QualType &CanTy = Ty.getCanonicalType(); - if (CanTy != Ty) { - OS << " (aka '"; - CanTy.print(OS, PP); - OS << "')"; + std::string TyStr = Ty.getAsString(PP); + OS << "'" << TyStr << "'"; + std::string CanTyStr = Ty.getCanonicalType().getAsString(PP); + if (CanTyStr != TyStr) { + OS << " (aka '" << CanTyStr << "')"; } } + } // namespace void describeCast(raw_ostream &OS, const CastExpr *CE, @@ -77,6 +76,14 @@ void describeCast(raw_ostream &OS, const CastExpr *CE, printType(OS, CE->getType(), PP); } +const DeclRegion *getAllocationDecl(const MemRegion *MR) { + if (const DeclRegion *DR = MR->getAs()) + return DR; + if (const ElementRegion *ER = MR->getAs()) + return getAllocationDecl(ER->getSuperRegion()); + return nullptr; +} + } // end of namespace: cheri } // end of namespace: ento diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h index 82a0eb06e44d..1dec89334b0f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h @@ -30,6 +30,8 @@ bool hasCapability(const QualType OrigTy, ASTContext &Ctx); void describeCast(raw_ostream &OS, const CastExpr *CE, const LangOptions &LangOpts); +const DeclRegion *getAllocationDecl(const MemRegion *MR); + } // end of namespace: cheri } // end of namespace: ento } // end of namespace: clang diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index e0904cc24de4..65b86afa5813 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -384,14 +384,6 @@ static void describeObjectType(raw_ostream &OS, const QualType &Ty, } } -const DeclRegion *getOriginalAllocation(const MemRegion *MR) { - if (const DeclRegion *DR = MR->getAs()) - return DR; - if (const ElementRegion *ER = MR->getAs()) - return getOriginalAllocation(ER->getSuperRegion()); - return nullptr; -} - unsigned int getFragileAlignment(const MemRegion *MR, const ProgramStateRef State, ASTContext& ASTCtx) { @@ -509,7 +501,7 @@ void PointerAlignmentChecker::checkBind(SVal L, SVal V, const Stmt *S, (isa(TR->getMemorySpace()) || isa(TR->StripCasts())); } - if (const DeclRegion *D = getOriginalAllocation(CapStorageReg)) + if (const DeclRegion *D = getAllocationDecl(CapStorageReg)) CapDstDecl = D->getDecl(); } @@ -650,7 +642,7 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, const MemRegion *CapStorageReg = SrcVal.getAsRegion(); const ValueDecl *CapSrcDecl = nullptr; if (CapStorageReg) { - if (const DeclRegion *D = getOriginalAllocation(CapStorageReg)) + if (const DeclRegion *D = getAllocationDecl(CapStorageReg)) CapSrcDecl = D->getDecl(); } @@ -686,7 +678,7 @@ void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, const MemRegion *CapStorageReg = DstVal.getAsRegion(); const ValueDecl *CapDstDecl = nullptr; if (CapStorageReg) { - if (const DeclRegion *D = getOriginalAllocation(CapStorageReg)) + if (const DeclRegion *D = getAllocationDecl(CapStorageReg)) CapDstDecl = D->getDecl(); } @@ -988,7 +980,7 @@ PointerAlignmentChecker::emitAlignmentWarning( unsigned FragileAlignment = 0; if (const MemRegion *MR = UnderalignedPtrVal.getAsRegion()) { FragileAlignment = getFragileAlignment(MR, C.getState(), C.getASTContext()); - if (const DeclRegion *OriginalAlloc = getOriginalAllocation(MR)) { + if (const DeclRegion *OriginalAlloc = getAllocationDecl(MR)) { MRDecl = OriginalAlloc->getDecl(); MRDeclLoc = PathDiagnosticLocation::create(MRDecl, C.getSourceManager()); } diff --git a/clang/test/Analysis/Checkers/CHERI/allocation.c b/clang/test/Analysis/Checkers/CHERI/allocation.c index 9a612177f82a..c9bbad0509ad 100644 --- a/clang/test/Analysis/Checkers/CHERI/allocation.c +++ b/clang/test/Analysis/Checkers/CHERI/allocation.c @@ -3,11 +3,11 @@ typedef __typeof__(sizeof(int)) size_t; extern void * malloc(size_t); - +void foo(void*); struct S1 { int *a[3]; - int *d[1]; + int *d[3]; }; struct S2 { @@ -17,28 +17,28 @@ struct S2 { struct S2 * test_1(int n1, int n2) { struct S1 *p1 = malloc(sizeof(struct S1)*n1 + sizeof(struct S2)*n2); - struct S2 *p2 = (struct S2 *)(p1+n1); // expected-warning{{Allocation partition}} - return p2; + struct S2 *p2 = (struct S2 *)(p1+n1); + return p2; // expected-warning{{Pointer to suballocation returned from function}} } -double buf[100] __attribute__((aligned(_Alignof(void*)))); -struct S2 * test_2(int n1) { +double buf[100] __attribute__((aligned(_Alignof(void*)))); // expected-note{{Original allocation}} +void test_2(int n1) { struct S1 *p1 = (struct S1 *)buf; // ? - struct S2 *p2 = (struct S2 *)(p1+n1); // expected-warning{{Allocation partition}} - return p2; + struct S2 *p2 = (struct S2 *)(p1+n1); + foo(p2); // expected-warning{{Pointer to suballocation passed to function}} } -struct S2 * test_3(int n1, int n2) { +void test_3(int n1, int n2) { struct S1 *p1 = malloc(sizeof(struct S1)*n1 + sizeof(struct S2)*n2); - struct S2 *p2 = (struct S2 *)(p1+n1); // expected-warning{{Allocation partition}} - return p2; + struct S2 *p2 = (struct S2 *)(p1+n1); + foo(p2); // expected-warning{{Pointer to suballocation passed to function}} } void array(int i, int j) { int a[100][200]; int (*p1)[200] = &a[i]; - int *p2 = p1[j]; // no warn - *p2 = 42; + int *p2 = p1[j]; + foo(p2); // no warn } struct S3 { @@ -48,8 +48,8 @@ struct S3 { struct S2 * first_field(void *p, int n1) { struct S3 *p3 = p; - struct S2 *p2 = (struct S2 *)(p3+n1); // no warn - return p2; + struct S2 *p2 = (struct S2 *)(p3+n1); + return p2; // no warn } struct S4 { @@ -59,6 +59,24 @@ struct S4 { int* flex_array(int len) { struct S4 *p = malloc(sizeof(struct S4) + len*sizeof(int)); - int *pB = (int*)(p + 1); // no warn - return pB; + int *pB = (int*)(p + 1); + return pB; // no warn +} + +void test_4(struct S2 *pS2) { + double a[100]; // expected-note{{Original allocation}} + double *p1 = a; + pS2->px = (int*)(p1 + 10); // expected-warning{{Pointer to suballocation escaped on assign}} +} + +void test_5(int n1, int n2) { + int *p1 = malloc(sizeof(struct S1)*n1 + sizeof(struct S2)*n2); + unsigned *p2 = (unsigned*)(p1+n1); + foo(p2); // no warn +} + +void test_6(int n1, int n2) { + struct S1 **p1 = malloc(sizeof(struct S1*)*n1 + sizeof(struct S2*)*n2); + struct S2 **p2 = (struct S2 **)(p1+n1); + foo(p2); // no warn } From bb559532758f0825a42c4c454d6ef58dbb4d8c64 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Thu, 23 May 2024 15:08:48 +0100 Subject: [PATCH 71/77] [CHERI_CSA] AllocationChecker: suppress for free --- .../StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp index 50b560fff075..f5f42dd582e2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp @@ -21,6 +21,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include using namespace clang; @@ -49,6 +50,11 @@ class AllocationChecker : public Checker, BugType BT_KnownReg{this, "Heap or static allocation partitioning", "CHERI portability"}; + const CallDescriptionSet IgnoreFnSet { + {"free", 1}, + }; + + class AllocPartitionBugVisitor : public BugReporterVisitor { public: AllocPartitionBugVisitor(const MemRegion *P, const MemRegion *A) @@ -288,6 +294,9 @@ void AllocationChecker::checkPostStmt(const CastExpr *CE, void AllocationChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { + if (IgnoreFnSet.contains(Call)) + return; + ProgramStateRef State = C.getState(); ExplodedNode *N = nullptr; bool Updated = false; From 7d72902492eec2ceb5d9577f5d938594c638de7e Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 24 May 2024 14:09:05 +0100 Subject: [PATCH 72/77] [CHERI_CSA] CHERI API Modelling --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 4 + .../Checkers/CHERI/CheriAPIModelling.cpp | 93 +++++++++++++++++++ .../StaticAnalyzer/Checkers/CMakeLists.txt | 1 + 3 files changed, 98 insertions(+) create mode 100644 clang/lib/StaticAnalyzer/Checkers/CHERI/CheriAPIModelling.cpp diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 81c40a689bd6..611d6a54ff51 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1748,6 +1748,10 @@ def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">, let ParentPackage = CHERI in { +def CheriAPIModelling : Checker<"CheriAPIModelling">, + HelpText<"Model CheriAPI">, + Documentation; + def ProvenanceSourceChecker : Checker<"ProvenanceSource">, HelpText<"Check expressions with ambiguous provenance source.">, CheckerOptions<[ diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CheriAPIModelling.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CheriAPIModelling.cpp new file mode 100644 index 000000000000..685344291528 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CheriAPIModelling.cpp @@ -0,0 +1,93 @@ +//===-- AllocationChecker.cpp - Allocation Checker -*- 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 +// +//===----------------------------------------------------------------------===// +// +// Model CHERI API +// +//===----------------------------------------------------------------------===// + +#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/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include + + +using namespace clang; +using namespace ento; + + +//namespace { + +class CheriAPIModelling : public Checker { +public: + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + + + typedef void (CheriAPIModelling::*FnCheck)(CheckerContext &, + const CallExpr *) const; + CallDescriptionMap Callbacks = { + {{"cheri_address_set", 2}, &CheriAPIModelling::evalAddrSet}, + {{"cheri_bounds_set", 2}, &CheriAPIModelling::evalBoundsSet}, + {{"cheri_bounds_set_exact", 2}, &CheriAPIModelling::evalBoundsSet}, + {{"cheri_perms_and", 2}, &CheriAPIModelling::evalBoundsSet}, + {{"cheri_tag_clear", 1}, &CheriAPIModelling::evalBoundsSet} + + }; + + void evalBoundsSet(CheckerContext &C, const CallExpr *CE) const; + void evalAddrSet(CheckerContext &C, const CallExpr *CE) const; +}; + +//} // namespace + +void CheriAPIModelling::evalBoundsSet(CheckerContext &C, + const CallExpr *CE) const { + auto State = C.getState(); + SVal Cap = C.getSVal(CE->getArg(0)); + State = State->BindExpr(CE, C.getLocationContext(), Cap); + C.addTransition(State); +} + +void CheriAPIModelling::evalAddrSet(CheckerContext &C, + const CallExpr *CE) const { + auto State = C.getState(); + SVal Addr = C.getSVal(CE->getArg(1)); + State = State->BindExpr(CE, C.getLocationContext(), Addr); + C.addTransition(State); +} + + + +bool CheriAPIModelling::evalCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return false; + + const FnCheck *Handler = Callbacks.lookup(Call); + if (!Handler) + return false; + + (this->**Handler)(C, CE); + return true; +} + +//===----------------------------------------------------------------------===// +// Checker registration. +//===----------------------------------------------------------------------===// + +void clang::ento::registerCheriAPIModelling(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool clang::ento::shouldRegisterCheriAPIModelling(const CheckerManager &Mgr) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index ebec5d6d493a..993f4b0fd61e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -26,6 +26,7 @@ add_clang_library(clangStaticAnalyzerCheckers CheckerDocumentation.cpp CHERI/AllocationChecker.cpp CHERI/CapabilityCopyChecker.cpp + CHERI/CheriAPIModelling.cpp CHERI/CHERIUtils.cpp CHERI/PointerSizeAssumptionsChecker.cpp CHERI/ProvenanceSourceChecker.cpp From f51699b49761bc5919d34e54baeeb5a06b47953b Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 24 May 2024 14:12:51 +0100 Subject: [PATCH 73/77] [CHERI_CSA] AllocationChecker: suppress for bounded suballocations --- .../Checkers/CHERI/AllocationChecker.cpp | 44 ++++++++++++++++--- .../test/Analysis/Checkers/CHERI/allocation.c | 10 ++++- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp index f5f42dd582e2..95115d8fcd94 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp @@ -44,6 +44,7 @@ using EscapePair = std::pair; namespace { class AllocationChecker : public Checker, check::PreCall, + check::PostCall, check::Bind, check::EndFunction> { BugType BT_Default{this, "Allocation partitioning", "CHERI portability"}; @@ -54,6 +55,11 @@ class AllocationChecker : public Checker, {"free", 1}, }; + const CallDescriptionSet CheriBoundsFnSet { + {"cheri_bounds_set", 2}, + {"cheri_bounds_set_exact", 2}, + }; + class AllocPartitionBugVisitor : public BugReporterVisitor { public: @@ -81,6 +87,7 @@ class AllocationChecker : public Checker, public: void checkPostStmt(const CastExpr *CE, CheckerContext &C) const; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; void checkEndFunction(const ReturnStmt *RS, CheckerContext &Ctx) const; @@ -96,6 +103,8 @@ class AllocationChecker : public Checker, REGISTER_MAP_WITH_PROGRAMSTATE(AllocMap, const MemRegion *, QualType) REGISTER_MAP_WITH_PROGRAMSTATE(ShiftMap, const MemRegion *, const MemRegion *) REGISTER_SET_WITH_PROGRAMSTATE(SuballocationSet, const MemRegion *) +REGISTER_SET_WITH_PROGRAMSTATE(BoundedSet, const MemRegion *) + namespace { std::pair getAllocationStart(const ASTContext &ASTCtx, @@ -234,21 +243,25 @@ void AllocationChecker::checkPostStmt(const CastExpr *CE, const MemRegion *MR = SrcVal.getAsRegion(); if (!MR) return; - const MemSpaceRegion *MemSpace = MR->getMemorySpace(); - if (!isa(MemSpace)) + + ProgramStateRef State = C.getState(); + if (State->contains(MR)) return; const ASTContext &ASTCtx = C.getASTContext(); - ProgramStateRef State = C.getState(); bool Updated = false; - std::pair StartPair = getAllocationStart(ASTCtx, MR, State); + const MemRegion *SR = StartPair.first; if (!isAllocation(SR)) return; bool ZeroShift = StartPair.second; + const MemSpaceRegion *MemSpace = SR->getMemorySpace(); + if (!isa(MemSpace)) + return; + SVal DstVal = C.getSVal(CE); const MemRegion *DMR = DstVal.getAsRegion(); if (MR->getAs() && (!DMR || !DMR->getAs())) { @@ -294,9 +307,9 @@ void AllocationChecker::checkPostStmt(const CastExpr *CE, void AllocationChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - if (IgnoreFnSet.contains(Call)) + if (IgnoreFnSet.contains(Call) || CheriBoundsFnSet.contains(Call)) return; - + ProgramStateRef State = C.getState(); ExplodedNode *N = nullptr; bool Updated = false; @@ -332,6 +345,25 @@ void AllocationChecker::checkPreCall(const CallEvent &Call, C.addTransition(State, N ? N : C.getPredecessor()); } +void AllocationChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + if (!CheriBoundsFnSet.contains(Call)) + return; + const MemRegion *MR = C.getSVal(Call.getArgExpr(0)).getAsRegion(); + const MemRegion *ResMR = C.getSVal(Call.getOriginExpr()).getAsRegion(); + if (!MR || !ResMR) + return; + + ProgramStateRef State = C.getState(); + if (!State->contains(MR) || + !State->contains(ResMR)) + return; + + State = State->remove(ResMR); + State = State->add(ResMR); + C.addTransition(State); +} + void AllocationChecker::checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const { const MemRegion *Dst = L.getAsRegion(); diff --git a/clang/test/Analysis/Checkers/CHERI/allocation.c b/clang/test/Analysis/Checkers/CHERI/allocation.c index c9bbad0509ad..ef83bd9c0f07 100644 --- a/clang/test/Analysis/Checkers/CHERI/allocation.c +++ b/clang/test/Analysis/Checkers/CHERI/allocation.c @@ -1,5 +1,5 @@ // RUN: %cheri_purecap_cc1 -analyze -verify %s \ -// RUN: -analyzer-checker=core,unix,alpha.cheri.Allocation +// RUN: -analyzer-checker=core,unix,alpha.cheri.Allocation,cheri.CheriAPIModelling typedef __typeof__(sizeof(int)) size_t; extern void * malloc(size_t); @@ -80,3 +80,11 @@ void test_6(int n1, int n2) { struct S2 **p2 = (struct S2 **)(p1+n1); foo(p2); // no warn } + +void *cheri_bounds_set(void *c, size_t x); +void test_7(int n1, int n2) { + struct S1 *p1 = malloc(sizeof(struct S1)*n1 + sizeof(struct S2)*n2); + struct S2 *p2 = (struct S2 *)(p1+n1); + struct S2 *p3 = cheri_bounds_set(p2, sizeof(struct S2)*n2); + foo(p3); // no-warn +} From aee174e73159b36bfd5143c0677a4f2e406a6eeb Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 24 May 2024 15:11:54 +0100 Subject: [PATCH 74/77] [CHERI_CSA] AllocationChecker: add ReportForUnknownAllocations option --- .../clang/StaticAnalyzer/Checkers/Checkers.td | 25 ++++++---- .../Checkers/CHERI/AllocationChecker.cpp | 46 +++++++++++++------ clang/test/Analysis/analyzer-config.c | 1 + 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index 611d6a54ff51..42b936036082 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -1772,15 +1772,15 @@ def ProvenanceSourceChecker : Checker<"ProvenanceSource">, def CapabilityCopyChecker : Checker<"CapabilityCopy">, HelpText<"Check tag-stripping memory copy.">, - CheckerOptions<[ - CmdLineOption - ]>, + CheckerOptions<[ + CmdLineOption + ]>, Documentation; def PointerSizeAssumptionsChecker : Checker<"PointerSizeAssumptions">, @@ -1797,6 +1797,13 @@ let ParentPackage = CHERIAlpha in { def AllocationChecker : Checker<"Allocation">, HelpText<"Suggest narrowing bounds for escaping suballocation capabilities">, + CheckerOptions<[ + CmdLineOption + ]>, Documentation; } // end alpha.cheri diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp index 95115d8fcd94..dfb943359cd9 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp @@ -48,7 +48,7 @@ class AllocationChecker : public Checker, check::Bind, check::EndFunction> { BugType BT_Default{this, "Allocation partitioning", "CHERI portability"}; - BugType BT_KnownReg{this, "Heap or static allocation partitioning", + BugType BT_UnknownReg{this, "Unknown allocation partitioning", "CHERI portability"}; const CallDescriptionSet IgnoreFnSet { @@ -91,6 +91,8 @@ class AllocationChecker : public Checker, void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; void checkEndFunction(const ReturnStmt *RS, CheckerContext &Ctx) const; + bool ReportForUnknownAllocations; + private: ExplodedNode *emitAllocationPartitionWarning(CheckerContext &C, const MemRegion *MR, @@ -122,7 +124,13 @@ std::pair getAllocationStart(const ASTContext &ASTCtx, return std::make_pair(R, ZeroShift); } -bool isAllocation(const MemRegion *R) { +bool isAllocation(const MemRegion *R, const AllocationChecker* Chk) { + if (!Chk->ReportForUnknownAllocations) { + const MemSpaceRegion *MemSpace = R->getMemorySpace(); + if (!isa(MemSpace)) + return false; + } + if (R->getAs()) return true; if (const TypedValueRegion *TR = R->getAs()) { @@ -214,12 +222,19 @@ ExplodedNode *AllocationChecker::emitAllocationPartitionWarning( CheckerContext &C, const MemRegion *MR, SourceRange SR, StringRef Msg = "") const { if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { - auto R = std::make_unique(BT_Default, Msg, ErrNode); - R->addRange(SR); - R->markInteresting(MR); const MemRegion *PrevAlloc = getAllocationStart(C.getASTContext(), MR, C.getState()).first; + const MemSpaceRegion *MS = + PrevAlloc ? PrevAlloc->getMemorySpace() : MR->getMemorySpace(); + const BugType &BT = + isa(MS) + ? BT_Default + : BT_UnknownReg; + auto R = std::make_unique(BT, Msg, ErrNode); + R->addRange(SR); + R->markInteresting(MR); + R->addVisitor(std::make_unique( PrevAlloc == MR ? nullptr : PrevAlloc, MR)); @@ -254,14 +269,10 @@ void AllocationChecker::checkPostStmt(const CastExpr *CE, getAllocationStart(ASTCtx, MR, State); const MemRegion *SR = StartPair.first; - if (!isAllocation(SR)) + if (!isAllocation(SR, this)) return; bool ZeroShift = StartPair.second; - const MemSpaceRegion *MemSpace = SR->getMemorySpace(); - if (!isa(MemSpace)) - return; - SVal DstVal = C.getSVal(CE); const MemRegion *DMR = DstVal.getAsRegion(); if (MR->getAs() && (!DMR || !DMR->getAs())) { @@ -290,10 +301,14 @@ void AllocationChecker::checkPostStmt(const CastExpr *CE, ->getUnqualifiedDesugaredType(); const Type *Ty2 = DstTy->getPointeeType()->getUnqualifiedDesugaredType(); if (!relatedTypes(ASTCtx, Ty1, Ty2)) { - State = State->add(SR); - if (DMR) + if (!State->contains(SR)) { + State = State->add(SR); + Updated = true; + } + if (DMR && !State->contains(DMR)) { State = State->add(DMR); - Updated = true; + Updated = true; + } } // else OK } // else ??? (ignore for now) } else { @@ -450,7 +465,10 @@ PathDiagnosticPieceRef AllocationChecker::AllocPartitionBugVisitor::VisitNode( //===----------------------------------------------------------------------===// void ento::registerAllocationChecker(CheckerManager &Mgr) { - Mgr.registerChecker(); + auto *Checker = Mgr.registerChecker(); + Checker->ReportForUnknownAllocations = + Mgr.getAnalyzerOptions().getCheckerBooleanOption( + Checker, "ReportForUnknownAllocations"); } bool ento::shouldRegisterAllocationChecker(const CheckerManager &Mgr) { diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index d13a4e0a847f..c7faec359f81 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -4,6 +4,7 @@ // CHECK: [config] // CHECK-NEXT: add-pop-up-notes = true // CHECK-NEXT: aggressive-binary-operation-simplification = false +// CHECK-NEXT: alpha.cheri.Allocation:ReportForUnknownAllocations = true // CHECK-NEXT: alpha.clone.CloneChecker:IgnoredFilesPattern = "" // CHECK-NEXT: alpha.clone.CloneChecker:MinimumCloneComplexity = 50 // CHECK-NEXT: alpha.clone.CloneChecker:ReportNormalClones = true From cac85bb7de84ebbf0ff65faa87dcda83d76abf5c Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 31 May 2024 10:26:11 +0100 Subject: [PATCH 75/77] [CHERI_CSA] AllocationChecker: disable for non-purecap --- .../Checkers/CHERI/AllocationChecker.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp index dfb943359cd9..f3b3a07d9829 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp @@ -252,6 +252,9 @@ ExplodedNode *AllocationChecker::emitAllocationPartitionWarning( void AllocationChecker::checkPostStmt(const CastExpr *CE, CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + if (CE->getCastKind() != CK_BitCast) return; SVal SrcVal = C.getSVal(CE->getSubExpr()); @@ -322,6 +325,9 @@ void AllocationChecker::checkPostStmt(const CastExpr *CE, void AllocationChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + if (IgnoreFnSet.contains(Call) || CheriBoundsFnSet.contains(Call)) return; @@ -362,6 +368,9 @@ void AllocationChecker::checkPreCall(const CallEvent &Call, void AllocationChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + if (!CheriBoundsFnSet.contains(Call)) return; const MemRegion *MR = C.getSVal(Call.getArgExpr(0)).getAsRegion(); @@ -381,6 +390,9 @@ void AllocationChecker::checkPostCall(const CallEvent &Call, void AllocationChecker::checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + const MemRegion *Dst = L.getAsRegion(); if (!Dst || !isa(Dst)) return; @@ -402,6 +414,9 @@ void AllocationChecker::checkBind(SVal L, SVal V, const Stmt *S, void AllocationChecker::checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + if (!RS) return; const Expr *RV = RS->getRetValue(); From f58077d23412ce827410bff7a0001715abc02d17 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Fri, 31 May 2024 16:39:45 +0100 Subject: [PATCH 76/77] [CHERI_CSA] Refactoring state cleanup for dead symbols & regions --- .../Checkers/CHERI/AllocationChecker.cpp | 22 +++++++++- .../Checkers/CHERI/CHERIUtils.cpp | 16 ++++--- .../Checkers/CHERI/CHERIUtils.h | 44 +++++++++++++++++-- .../Checkers/CHERI/CapabilityCopyChecker.cpp | 33 ++++++++++---- .../CHERI/ProvenanceSourceChecker.cpp | 19 ++------ .../Checkers/PointerAlignmentChecker.cpp | 16 +++---- 6 files changed, 103 insertions(+), 47 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp index f3b3a07d9829..c7151d860c3e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp @@ -18,10 +18,10 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" -#include using namespace clang; @@ -46,7 +46,8 @@ class AllocationChecker : public Checker, check::PreCall, check::PostCall, check::Bind, - check::EndFunction> { + check::EndFunction, + check::DeadSymbols> { BugType BT_Default{this, "Allocation partitioning", "CHERI portability"}; BugType BT_UnknownReg{this, "Unknown allocation partitioning", "CHERI portability"}; @@ -90,6 +91,7 @@ class AllocationChecker : public Checker, void checkPostCall(const CallEvent &Call, CheckerContext &C) const; void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; void checkEndFunction(const ReturnStmt *RS, CheckerContext &Ctx) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; bool ReportForUnknownAllocations; @@ -439,6 +441,22 @@ void AllocationChecker::checkEndFunction(const ReturnStmt *RS, } } +void AllocationChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + + ProgramStateRef State = C.getState(); + bool Removed = false; + State = cleanDead(State, SymReaper, Removed); + State = cleanDead(State, SymReaper, Removed); + State = cleanDead(State, SymReaper, Removed); + State = cleanDead(State, SymReaper, Removed); + + if (Removed) + C.addTransition(State); +} + PathDiagnosticPieceRef AllocationChecker::AllocPartitionBugVisitor::VisitNode( const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &BR) { diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp index 4add39140f4b..0f0ab8fe6833 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp @@ -7,6 +7,10 @@ //===----------------------------------------------------------------------===// #include "CHERIUtils.h" +#include + +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" + namespace clang { namespace ento { @@ -54,8 +58,7 @@ bool hasCapability(const QualType OrigTy, ASTContext &Ctx) { namespace { -void printType(raw_ostream &OS, const QualType &Ty, - const PrintingPolicy &PP) { +void printType(raw_ostream &OS, const QualType &Ty, const PrintingPolicy &PP) { std::string TyStr = Ty.getAsString(PP); OS << "'" << TyStr << "'"; std::string CanTyStr = Ty.getCanonicalType().getAsString(PP); @@ -67,7 +70,7 @@ void printType(raw_ostream &OS, const QualType &Ty, } // namespace void describeCast(raw_ostream &OS, const CastExpr *CE, - const LangOptions &LangOpts) { + const LangOptions &LangOpts) { const PrintingPolicy &PP = PrintingPolicy(LangOpts); OS << (dyn_cast(CE) ? "implicit" : "explicit"); OS << " cast from "; @@ -84,7 +87,6 @@ const DeclRegion *getAllocationDecl(const MemRegion *MR) { return nullptr; } - -} // end of namespace: cheri -} // end of namespace: ento -} // end of namespace: clang \ No newline at end of file +} // namespace cheri +} // namespace ento +} // namespace clang \ No newline at end of file diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h index 1dec89334b0f..0c552d971d2e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h @@ -10,6 +10,8 @@ #define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_CHERI_CHERIUTILS_H #include "clang/StaticAnalyzer/Core/Checker.h" +#include +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" namespace clang { namespace ento { @@ -32,8 +34,44 @@ void describeCast(raw_ostream &OS, const CastExpr *CE, const DeclRegion *getAllocationDecl(const MemRegion *MR); -} // end of namespace: cheri -} // end of namespace: ento -} // end of namespace: clang +} // namespace cheri + +template +inline typename ProgramStateTrait::key_type +getKey(const std::pair::key_type, + typename ProgramStateTrait::value_type> &P) { + return P.first; +} + +template +inline typename ProgramStateTrait::key_type +getKey(const typename ProgramStateTrait::key_type &K) { + return K; +} + +inline bool isLive(SymbolReaper &SymReaper, const MemRegion *MR) { + return SymReaper.isLiveRegion(MR); +} + +inline bool isLive(SymbolReaper &SymReaper, SymbolRef Sym) { + return SymReaper.isLive(Sym); +} + +template +ProgramStateRef cleanDead(ProgramStateRef State, SymbolReaper &SymReaper, + bool &Removed) { + const typename ProgramStateTrait::data_type &Map = State->get(); + for (const auto &E : Map) { + const typename ProgramStateTrait::key_type &K = getKey(E); + if (isLive(SymReaper, K)) + continue; + State = State->remove(K); + Removed = true; + } + return State; +} + +} // namespace ento +} // namespace clang #endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_CHERI_CHERIUTILS_H diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp index a88d2a1ee3df..446703162030 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -14,13 +14,13 @@ #include "CHERIUtils.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" +#include #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include -#include using namespace clang; using namespace ento; @@ -57,7 +57,8 @@ class CapabilityCopyChecker :public Checker, check::PostStmt, check::BranchCondition, - check::PreCall> { + check::PreCall, + check::DeadSymbols> { BugType UseCapAsNonCap{this, "Part of capability value used in binary operator", @@ -83,6 +84,7 @@ class CapabilityCopyChecker :public Checkerget(); - for (auto &&V : BoundVarList) { + auto BoundVarSet = C.getState()->get(); + for (auto &&V : BoundVarSet) { auto SmallLoop = SVB.evalBinOpNN(C.getState(), clang::BO_GE, nonloc::SymbolVal(V), ItVal, SVB.getConditionType()); @@ -485,8 +487,6 @@ bool checkForWhileBoundVar(const Stmt *Condition, CheckerContext &C, if (SymbolRef ISym = C.getSVal(L).getAsSymbol()) { if (ThenSt) ThenSt = ThenSt->add(ISym); - if (ElseSt) - ElseSt = ElseSt->set(llvm::ImmutableList()); } else return false; } @@ -627,6 +627,23 @@ void CapabilityCopyChecker::checkPreCall(const CallEvent &Call, C.addTransition(State); } +void CapabilityCopyChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + + ProgramStateRef State = C.getState(); + bool Removed = false; + + State = cleanDead(State, SymReaper, Removed); + State = cleanDead(State, SymReaper, Removed); + State = cleanDead(State, SymReaper, Removed); + State = cleanDead(State, SymReaper, Removed); + + if (Removed) + C.addTransition(State); +} + void ento::registerCapabilityCopyChecker(CheckerManager &mgr) { auto *Checker = mgr.registerChecker(); Checker->ReportForCharPtr = mgr.getAnalyzerOptions().getCheckerBooleanOption( diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp index 79dd895eea2b..257a1d4a0f67 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -515,23 +515,10 @@ void ProvenanceSourceChecker::checkDeadSymbols(SymbolReaper &SymReaper, return; ProgramStateRef State = C.getState(); - bool Removed = false; - const InvalidCapTy &Set = State->get(); - for (const auto &Sym : Set) { - if (!SymReaper.isDead(Sym)) - continue; - State = State->remove(Sym); - Removed = true; - } - - const AmbiguousProvenanceSymTy &Set2 = State->get(); - for (const auto &Sym : Set2) { - if (!SymReaper.isDead(Sym)) - continue; - State = State->remove(Sym); - Removed = true; - } + State = cleanDead(State, SymReaper, Removed); + State = cleanDead(State, SymReaper, Removed); + State = cleanDead(State, SymReaper, Removed); if (Removed) C.addTransition(State); diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp index 65b86afa5813..d6533a0b2769 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -29,13 +29,13 @@ //===----------------------------------------------------------------------===// #include "CHERI/CHERIUtils.h" +#include #include "clang/ASTMatchers/ASTMatchers.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include #include #include #include +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" using namespace clang; using namespace ento; @@ -914,17 +914,11 @@ void PointerAlignmentChecker::checkPostStmt(const BinaryOperator *BO, void PointerAlignmentChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { - bool Updated = false; ProgramStateRef State = C.getState(); + bool Updated = false; - TrailingZerosMapTy TZMap = State->get(); - for (TrailingZerosMapTy::iterator I = TZMap.begin(), E = TZMap.end(); - I != E; ++I) { - if (SymReaper.isDead(I->first)) { - State = State->remove(I->first); - Updated = true; - } - } + State = cleanDead(State, SymReaper, Updated); + State = cleanDead(State, SymReaper, Updated); if (Updated) C.addTransition(State); From 52238fda48f254503bf36d065863fa3e64e67ee1 Mon Sep 17 00:00:00 2001 From: Irina Dudina Date: Wed, 12 Jun 2024 16:28:35 +0100 Subject: [PATCH 77/77] [CHERI_CSA] SubObjectRepresentability: support other CHERI targets --- .../SubObjectRepresentabilityChecker.cpp | 65 +++++++++++-------- .../CHERI/subobject-representability-mips64.c | 32 +++++++++ 2 files changed, 71 insertions(+), 26 deletions(-) create mode 100644 clang/test/Analysis/Checkers/CHERI/subobject-representability-mips64.c diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp index 861e105924ca..045505168e2c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp @@ -31,6 +31,12 @@ using namespace clang; using namespace ento; namespace { + +template +std::unique_ptr checkFieldImpl(const FieldDecl *D, + BugReporter &BR, + const BugType &BT); + class SubObjectRepresentabilityChecker : public Checker, check::ASTCodeBody> { BugType BT_1{this, "Field with imprecise subobject bounds", @@ -43,6 +49,21 @@ class SubObjectRepresentabilityChecker BugReporter &BR) const; void checkASTCodeBody(const Decl *D, AnalysisManager &mgr, BugReporter &BR) const; + +private: + + using CheckFieldFn = std::function( + const FieldDecl *D, BugReporter &BR, const BugType &BT)>; + + const std::map CheckFieldFnMap { + {llvm::Triple::aarch64, &checkFieldImpl}, + {llvm::Triple::mips, &checkFieldImpl}, + {llvm::Triple::mips64, &checkFieldImpl}, + {llvm::Triple::riscv32, &checkFieldImpl}, + {llvm::Triple::riscv64, &checkFieldImpl} + }; + + CheckFieldFn getCheckFieldFn(ASTContext &ASTCtx) const; }; } //namespace @@ -156,42 +177,33 @@ std::unique_ptr checkFieldImpl(const FieldDecl *D, return nullptr; } -std::unique_ptr checkField(const FieldDecl *D, - BugReporter &BR, - const BugType &BT) { - // TODO: other targets - return checkFieldImpl(D, BR, BT); -} - -bool supportedTarget(const ASTContext &C) { - const TargetInfo &TI = C.getTargetInfo(); - return TI.areAllPointersCapabilities() - && TI.getTriple().isAArch64(); // morello -} - } // namespace +SubObjectRepresentabilityChecker::CheckFieldFn +SubObjectRepresentabilityChecker::getCheckFieldFn(ASTContext &ASTCtx) const { + const TargetInfo &TI = ASTCtx.getTargetInfo(); + if (!TI.areAllPointersCapabilities()) + return nullptr; + + auto It = CheckFieldFnMap.find(TI.getTriple().getArch()); + if (It == CheckFieldFnMap.end()) + return nullptr; + return It->second; +} void SubObjectRepresentabilityChecker::checkASTDecl(const RecordDecl *R, AnalysisManager &mgr, BugReporter &BR) const { - if (!supportedTarget(mgr.getASTContext())) - return; + CheckFieldFn checkField = getCheckFieldFn(mgr.getASTContext()); + if (!checkField) + return; // skip this target if (!R->isCompleteDefinition() || R->isDependentType()) return; if (!R->getLocation().isValid()) return; - - /* - SrcMgr::CharacteristicKind Kind = - BR.getSourceManager().getFileCharacteristic(Location); - // Ignore records in system headers - if (Kind != SrcMgr::C_User) - return; - */ - + for (FieldDecl *D : R->fields()) { auto Report = checkField(D, BR, BT_1); if (Report) @@ -202,8 +214,9 @@ void SubObjectRepresentabilityChecker::checkASTDecl(const RecordDecl *R, void SubObjectRepresentabilityChecker::checkASTCodeBody(const Decl *D, AnalysisManager &mgr, BugReporter &BR) const { - if (!supportedTarget(mgr.getASTContext())) - return; + CheckFieldFn checkField = getCheckFieldFn(mgr.getASTContext()); + if (!checkField) + return; // skip this target using namespace ast_matchers; auto Member = memberExpr().bind("member"); diff --git a/clang/test/Analysis/Checkers/CHERI/subobject-representability-mips64.c b/clang/test/Analysis/Checkers/CHERI/subobject-representability-mips64.c new file mode 100644 index 000000000000..f5a50a265c3d --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/subobject-representability-mips64.c @@ -0,0 +1,32 @@ +// RUN: %cheri_purecap_cc1 -analyze -verify %s \ +// RUN: -analyzer-checker=core,cheri.SubObjectRepresentability + +struct R1 { + struct { + struct { + char c; + char a[0x9FF]; // no warn + } f1good; + struct { + char c; // expected-note{{}} + char a[0x1000]; // expected-warning{{Field 'a' of type 'char[4096]' (size 4096) requires 8 byte alignment for precise bounds; field offset is 1}} + } f2bad; + struct { + int c[2]; + char a[0x1000]; // no warn + } f3good __attribute__((aligned(8))); + } s2; +} s1; + +struct S2 { + int x[3]; + int *px; +}; + +struct R2 { + char x[0x50]; // expected-note{{16/80}} + struct S2 s2; // expected-note{{32/32 bytes exposed (may expose capability!)}} + char c; // expected-note{{1}} + char a[0x8000]; // expected-warning{{Field 'a' of type 'char[32768]' (size 32768) requires 64 byte alignment for precise bounds; field offset is 113 (aligned to 1); Current bounds: 64-32896}} + char y[32]; // expected-note{{15/32}} +};