diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index cf8cec3b13c3..42b936036082 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. //===----------------------------------------------------------------------===// @@ -1664,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 //===----------------------------------------------------------------------===// @@ -1734,3 +1741,69 @@ def UncountedLocalVarsChecker : Checker<"UncountedLocalVarsChecker">, Documentation; } // end alpha.webkit + +//===----------------------------------------------------------------------===// +// CHERI checkers. +//===----------------------------------------------------------------------===// + +let ParentPackage = CHERI in { + +def CheriAPIModelling : Checker<"CheriAPIModelling">, + HelpText<"Model CheriAPI">, + Documentation; + +def ProvenanceSourceChecker : Checker<"ProvenanceSource">, + HelpText<"Check expressions with ambiguous provenance source.">, + CheckerOptions<[ + CmdLineOption, + CmdLineOption + ]>, + Documentation; + +def CapabilityCopyChecker : Checker<"CapabilityCopy">, + HelpText<"Check tag-stripping memory copy.">, + CheckerOptions<[ + CmdLineOption + ]>, + Documentation; + +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 AllocationChecker : Checker<"Allocation">, + HelpText<"Suggest narrowing bounds for escaping suballocation capabilities">, + CheckerOptions<[ + CmdLineOption + ]>, + Documentation; + +} // end alpha.cheri 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) { 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/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/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/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); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index a8307a077237..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"); @@ -3237,6 +3239,26 @@ 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"); + + // 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"); + } + // Default nullability checks. CmdArgs.push_back("-analyzer-checker=nullability.NullPassedToNonnull"); CmdArgs.push_back("-analyzer-checker=nullability.NullReturnedFromNonnull"); @@ -4986,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/AllocationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp new file mode 100644 index 000000000000..c7151d860c3e --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/AllocationChecker.cpp @@ -0,0 +1,509 @@ +//===-- 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/Checkers/BuiltinCheckerRegistration.h" +#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" + + +using namespace clang; +using namespace ento; +using namespace cheri; + + +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::PostCall, + check::Bind, + check::EndFunction, + check::DeadSymbols> { + BugType BT_Default{this, "Allocation partitioning", "CHERI portability"}; + BugType BT_UnknownReg{this, "Unknown allocation partitioning", + "CHERI portability"}; + + const CallDescriptionSet IgnoreFnSet { + {"free", 1}, + }; + + const CallDescriptionSet CheriBoundsFnSet { + {"cheri_bounds_set", 2}, + {"cheri_bounds_set_exact", 2}, + }; + + + class AllocPartitionBugVisitor : public BugReporterVisitor { + public: + 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(PrevAlloc); + ID.AddPointer(SubAlloc); + + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; + + private: + 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 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; + +private: + 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 *) +REGISTER_SET_WITH_PROGRAMSTATE(BoundedSet, const MemRegion *) + + +namespace { +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(ASTCtx, Base, State, + ZeroShift && ER->getIndex().isZeroConstant()); + } + if (const auto *OrigR = State->get(R)) { + return std::make_pair(*OrigR, false); + } + return std::make_pair(R, ZeroShift); +} + +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()) { + return TR->getValueType()->isArrayType(); + } + return false; +} + +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(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(ASTCtx, FFTy, 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()) { + 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; + } + return false; +} + +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 (reportForType(Ty)) + return Ty; + } + return None; +} + +} // namespace + +ExplodedNode *AllocationChecker::emitAllocationPartitionWarning( + CheckerContext &C, const MemRegion *MR, SourceRange SR, + StringRef Msg = "") const { + if (ExplodedNode *ErrNode = C.generateNonFatalErrorNode()) { + + 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)); + + 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; + } + return nullptr; +} + +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()); + const MemRegion *MR = SrcVal.getAsRegion(); + if (!MR) + return; + + ProgramStateRef State = C.getState(); + if (State->contains(MR)) + return; + + const ASTContext &ASTCtx = C.getASTContext(); + bool Updated = false; + std::pair StartPair = + getAllocationStart(ASTCtx, MR, State); + + const MemRegion *SR = StartPair.first; + if (!isAllocation(SR, this)) + 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 (!reportForType(DstTy)) + 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(ASTCtx, Ty1, Ty2)) { + if (!State->contains(SR)) { + State = State->add(SR); + Updated = true; + } + if (DMR && !State->contains(DMR)) { + State = State->add(DMR); + Updated = true; + } + } // else OK + } // else ??? (ignore for now) + } else { + State = State->set(SR, DstTy); + Updated = true; + } + + if (Updated) + C.addTransition(State); +} + +void AllocationChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (!isPureCapMode(C.getASTContext())) + return; + + if (IgnoreFnSet.contains(Call) || CheriBoundsFnSet.contains(Call)) + return; + + 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::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(); + 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 { + if (!isPureCapMode(C.getASTContext())) + return; + + 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 (!isPureCapMode(C.getASTContext())) + return; + + 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; + } + } +} + +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) { + const Stmt *S = N->getStmtForDiagnostics(); + if (!S) + 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); + + 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; +} + +//===----------------------------------------------------------------------===// +// Checker registration. +//===----------------------------------------------------------------------===// + +void ento::registerAllocationChecker(CheckerManager &Mgr) { + auto *Checker = Mgr.registerChecker(); + Checker->ReportForUnknownAllocations = + Mgr.getAnalyzerOptions().getCheckerBooleanOption( + Checker, "ReportForUnknownAllocations"); +} + +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 new file mode 100644 index 000000000000..0f0ab8fe6833 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.cpp @@ -0,0 +1,92 @@ +//===-- 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" +#include + +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.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); +} + +CharUnits getCapabilityTypeSize(ASTContext &ASTCtx) { + return ASTCtx.getTypeSizeInChars(ASTCtx.VoidPtrTy); +} + +CharUnits getCapabilityTypeAlign(ASTContext &ASTCtx) { + return ASTCtx.getTypeAlignInChars(ASTCtx.VoidPtrTy); +} + +bool isGenericPointerType(const QualType T, bool AcceptCharPtr) { + return T->isVoidPointerType() || (AcceptCharPtr && T->isPointerType() && + 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; +} + +namespace { + +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); + if (CanTyStr != TyStr) { + OS << " (aka '" << CanTyStr << "')"; + } +} + +} // 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 "; + printType(OS, CE->getSubExpr()->getType(), PP); + OS << " to "; + 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; +} + +} // 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 new file mode 100644 index 000000000000..0c552d971d2e --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CHERIUtils.h @@ -0,0 +1,77 @@ +//===-- 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" +#include +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" + +namespace clang { +namespace ento { +namespace cheri { + +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, bool AcceptCharPtr = true); + +bool hasCapability(const QualType OrigTy, ASTContext &Ctx); + +void describeCast(raw_ostream &OS, const CastExpr *CE, + const LangOptions &LangOpts); + +const DeclRegion *getAllocationDecl(const MemRegion *MR); + +} // 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 new file mode 100644 index 000000000000..446703162030 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/CapabilityCopyChecker.cpp @@ -0,0 +1,655 @@ +//===-- 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 "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" + +using namespace clang; +using namespace ento; +using namespace cheri; + +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::PostStmt, + check::BranchCondition, + check::PreCall, + check::DeadSymbols> { + + 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; + const CallDescriptionMap CStringFn { + {{"strlen", 1}, 1}, + {{"strdup", 1}, 1}, + {{"strcpy", 2}, 3}, + {{"strcat", 2}, 3} + }; + +public: + 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 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; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + + bool ReportForCharPtr = false; + +private: + ExplodedNode *checkBinaryOpArg(CheckerContext &C, const Expr* E) const; + +}; +} // namespace + +REGISTER_SET_WITH_PROGRAMSTATE(VoidPtrArgDeref, const MemRegion *) +REGISTER_SET_WITH_PROGRAMSTATE(UnalignedPtr, const MemRegion *) +REGISTER_SET_WITH_PROGRAMSTATE(CString, const MemRegion *) +REGISTER_SET_WITH_PROGRAMSTATE(WhileBoundVar, SymbolRef) + +namespace { + +bool isNonCapScalarType(QualType T, ASTContext &C) { + if (!T->isScalarType()) + return false; + if (T->isCHERICapabilityType(C, true)) + return false; + return true; +} + +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 stripNonCapShift(ER->getSuperRegion(), ASTCtx); +} + +bool isVoidOrCharPtrArgRegion(const MemRegion *Reg, bool AcceptCharPtr) { + if (!Reg) + return false; + + // 1. Reg is pointed by void* symbol + const SymbolicRegion *SymReg = Reg->getSymbolicBase(); + if (!SymReg) + return false; + + SymbolRef Sym = SymReg->getSymbol(); + if (!isGenericPointerType(Sym->getType(), AcceptCharPtr)) + return false; + + // 2. void* symbol is function argument + const MemRegion *BaseRegOrigin = Sym->getOriginRegion(); + return BaseRegOrigin && + BaseRegOrigin->getMemorySpace()->hasStackParametersStorage(); +} + +CHERITagState getTagState(SVal Val, CheckerContext &C, bool AcceptCharPtr, + bool AcceptUnaligned = true) { + 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 ProgramStateRef S = C.getState(); + const MemRegion *SuperReg = stripNonCapShift(MR, C.getASTContext()); + if (isa(SuperReg)) + return CHERITagState::getUnknown(); // not a part of capability + 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, AcceptCharPtr); + } + } + } + + return CHERITagState::getUnknown(); +} + +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)) + 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, AcceptCharPtr)) + 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; + const ASTContext &ASTCtx = C.getASTContext(); + if (!isPureCapMode(ASTCtx)) + return; + + const MemRegion *R = l.getAsRegion(); + if (!R || !R->hasStackParametersStorage()) + return; + + // Get ArgVal + ProgramStateRef State = C.getState(); + QualType const Ty = l.getType(ASTCtx); + QualType const ArgValTy = Ty->getPointeeType(); + if (!isGenericPointerType(ArgValTy, ReportForCharPtr)) + return; + + /* Loading VoidPtr function argument */ + 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 + const SVal ArgValDeref = State->getSVal(ArgValAsRegion, ASTCtx.CharTy); + + bool T; + if (const auto *ArgValDerefAsRegion = ArgValDeref.getAsRegion()) { + State = State->add(ArgValDerefAsRegion); + T = true; + } else if (ArgValDeref.getAsSymbol()) { + 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); +} + +namespace { + + +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)); +} + +bool isInsideSmallConstantBoundLoop(const Stmt *S, CheckerContext &C, long N) { + SValBuilder &SVB = C.getSValBuilder(); + const NonLoc &ItVal = SVB.makeIntVal(N, true); + + auto BoundVarSet = C.getState()->get(); + for (auto &&V : BoundVarSet) { + 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; +} + +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"); + 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, bool AcceptCharPtr) { + auto ExprPtrReg = isCapAlignCheck(BO, C); + if (!ExprPtrReg) + return false; + + if (!isVoidOrCharPtrArgRegion(ExprPtrReg, AcceptCharPtr)) + 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(); + if (!isPureCapMode(ASTCtx)) + return; + + const MemRegion *MR = L.getAsRegion(); + if (!MR) + return; + + const QualType &PointeeTy = L.getType(ASTCtx)->getPointeeType(); + if (!isNonCapScalarType(PointeeTy, ASTCtx)) + return; + /* Non-capability scalar store */ + + const CHERITagState &ValTag = getTagState(V, C, ReportForCharPtr); + if (!ValTag.isTagged() && !ValTag.mayBeTagged()) + return; + + if (!isCapabilityStorage(C, MR, ReportForCharPtr)) + return; + + if (ValTag.mayBeTagged() && !isPartOfCopyingSequence(PointeeTy, V, S, C)) + 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( + StoreCapAsNonCap, "Tag-stripping store of a capability", ErrNode); + W->addRange(S->getSourceRange()); + C.emitReport(std::move(W)); + } +} + +namespace { +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::internal; + +ProgramStateRef markOriginAsCStringForMatch(const Stmt *E, Matcher D, + CheckerContext &C, + bool AcceptCharPtr) { + 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, AcceptCharPtr)) { + /* 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); + } 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, ReportForCharPtr); + 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, ReportForCharPtr); + if (N) + C.addTransition(N); +} + +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; + + 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 nullptr; + const MemRegion *OrigRegion = Sym->getOriginRegion(); + if (!OrigRegion) + return nullptr; + const MemRegion *SReg = stripNonCapShift(OrigRegion, C.getASTContext()); + const SVal &WiderVal = C.getState()->getSVal(SReg, ASTCtx.CharTy); + MR = WiderVal.getAsRegion(); + if (!MR) + return nullptr; + } + + 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 ErrNode; + } + } + return nullptr; +} + +void CapabilityCopyChecker::checkPostStmt(const BinaryOperator *BO, + CheckerContext &C) const { + const ASTContext &ASTCtx = C.getASTContext(); + if (!isPureCapMode(ASTCtx)) + return; + + /* Check for capability repr bytes used in arithmetic */ + ExplodedNode *N = nullptr; + if (!BO->isAssignmentOp() || BO->isCompoundAssignmentOp()) { + if (!(N = checkBinaryOpArg(C, BO->getLHS()))) + N = checkBinaryOpArg(C, BO->getRHS()); + } + if (!N) + N = C.getPredecessor(); + ProgramStateRef State = N->getState(); + + /* CString character check */ + using namespace clang::ast_matchers; + 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, ReportForCharPtr); + if (NewState) + State = NewState; + + bool updated = handleAlignmentCheck(BO, State, C, ReportForCharPtr); + if (!updated && NewState) + C.addTransition(NewState); +} + +void CapabilityCopyChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (!Call.isGlobalCFunction()) + return; + + const int *StrParams = CStringFn.lookup(Call); + if (!StrParams) + 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; + } + + 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( + Checker, "ReportForCharPtr"); +} + +bool ento::shouldRegisterCapabilityCopyChecker(const CheckerManager &Mgr) { + return true; +} 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/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/CHERI/ProvenanceSourceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp new file mode 100644 index 000000000000..257a1d4a0f67 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/ProvenanceSourceChecker.cpp @@ -0,0 +1,618 @@ +//===-- 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 "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" + + +using namespace clang; +using namespace ento; +using namespace cheri; + +namespace { +class ProvenanceSourceChecker : public Checker, + check::PreStmt, + check::PostStmt, + check::PostStmt, + check::DeadSymbols> { + 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"}; + + BugType AmbigProvAsPtrBugType{this, + "Capability with ambiguous provenance used as pointer", + "CHERI portability"}; + BugType InvalidCapPtrBugType{this, + "NULL-derived capability used as pointer", + "CHERI portability"}; + BugType ProvenanceLossBugType{this, + "NULL-derived capability: loss of provenance", + "CHERI portability"}; + +private: + class InvalidCapBugVisitor : public BugReporterVisitor { + public: + InvalidCapBugVisitor(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: + 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; + + bool ShowFixIts = false; + bool ReportForAmbiguousProvenance = true; + +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, + bool RHSIsAddr, + bool RHSIsNullDerived) const; + + static void propagateProvenanceInfo(ExplodedNode *N, + const Expr *E, + CheckerContext &C, + bool IsInvalidCap, + const NoteTag* Tag); +}; +} // namespace + +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) + +namespace { +bool isIntegerToIntCapCast(const CastExpr *CE) { + if (CE->getCastKind() != CK_IntegralCast) + return false; + if (!CE->getType()->isIntCapType() || + CE->getSubExpr()->getType()->isIntCapType()) + return false; + 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; + if (!CE->getType()->isIntCapType()) + return false; + return true; +} + +} // namespace + +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) + 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 const State = C.getState(); + C.addTransition(State->set(State->get() + 1)); + } +} + +namespace { + +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; +} + +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; +} + +bool isAddress(const SVal &Val) { + if (!Val.getAsLocSymbol(true)) + return false; + + if (const auto &LocAsInt = Val.getAs()) + return LocAsInt->hasProvenance(); + + return true; +} + +bool isNoProvToPtrCast(const CastExpr *CE) { + if (!CE->getType()->isPointerType()) + return false; + + const Expr *Src = CE->getSubExpr(); + 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 { + if (!isPureCapMode(C.getASTContext())) + return; + + 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( + 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 (SrcVal.getAs()) + R = std::make_unique( + ProvenanceLossBugType, "NULL-derived capability: loss of provenance", + 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 + return; + + R->markInteresting(SrcVal); + R->addRange(CE->getSourceRange()); + C.emitReport(std::move(R)); +} + +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(); + if (!OrigType->isCHERICapabilityType(Ctx, true)) + return true; + } + return false; +} + +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 {}; +} + +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( + 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; + } + + 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 << "; consider indicating the provenance-carrying argument " + "explicitly by casting the other argument to '" + << (BO->getType()->isUnsignedIntegerType() ? "size_t" : "ptrdiff_t") + << "'. Note: along this path, "; + + 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 (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(BT, 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()) + 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::propagateProvenanceInfo( + ExplodedNode *N, const Expr *E, CheckerContext &C, + bool IsInvalidCap, const NoteTag* Tag) { + + ProgramStateRef State = N->getState(); + SVal ResVal = C.getSVal(E); + if (ResVal.isUnknown()) { + const LocationContext *LCtx = C.getLocationContext(); + ResVal = C.getSValBuilder().conjureSymbolVal( + nullptr, E, LCtx, E->getType(), C.blockCount()); + State = State->BindExpr(E, LCtx, ResVal); + } + + if (SymbolRef ResSym = ResVal.getAsSymbol()) + 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 + + C.addTransition(State, N, Tag); +} + +// 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 { + if (!isPureCapMode(C.getASTContext())) + return; + + BinaryOperatorKind const OpCode = BO->getOpcode(); + if (!isArith(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 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) + N = C.getPredecessor(); + InvalidCap = false; + } else if (IsSub && LHSIsAddr && RHSIsAddr) { + N = C.getPredecessor(); + InvalidCap = true; + } else if (LHSIsNullDerived && (RHSIsNullDerived || IsSub)) { + N = C.getPredecessor(); + InvalidCap = true; + } else + return; + + // Propagate info for result + 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, + 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); + + if (Removed) + C.addTransition(State); +} + +PathDiagnosticPieceRef +ProvenanceSourceChecker::InvalidCapBugVisitor::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 (Sym != N->getSVal(CE).getAsSymbol()) + return nullptr; + 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; + 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; + 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)) + return nullptr; + + 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; + + // 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) { + auto *Checker = mgr.registerChecker(); + Checker->ShowFixIts = mgr.getAnalyzerOptions().getCheckerBooleanOption( + Checker, "ShowFixIts"); + Checker->ReportForAmbiguousProvenance = + mgr.getAnalyzerOptions().getCheckerBooleanOption( + Checker, "ReportForAmbiguousProvenance"); +} + +bool ento::shouldRegisterProvenanceSourceChecker(const CheckerManager &Mgr) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp new file mode 100644 index 000000000000..045505168e2c --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CHERI/SubObjectRepresentabilityChecker.cpp @@ -0,0 +1,271 @@ +// ==-- 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 "CHERIUtils.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" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/raw_ostream.h" + +#include "llvm/CHERI/cheri-compressed-cap/cheri_compressed_cap.h" + +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", + "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; + +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 + +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; +} + +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; +} + +template +uint64_t getRepresentableAlignment(uint64_t Size) { + return ~Handler::representable_mask(Size) + 1; +} + +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 = getRepresentableAlignment(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 << ");"; + + const RecordDecl *Parent = D->getParent(); + uint64_t ParentSize = ASTCtx.getTypeSize(Parent->getTypeForDecl()) / 8; + 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 + // 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()); + + Report = reportExposedFields(D, ASTCtx, BR, Base, Top, std::move(Report)); + + return Report; + } + } + + return nullptr; +} + +} // 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 { + CheckFieldFn checkField = getCheckFieldFn(mgr.getASTContext()); + if (!checkField) + return; // skip this target + + if (!R->isCompleteDefinition() || R->isDependentType()) + return; + + if (!R->getLocation().isValid()) + return; + + for (FieldDecl *D : R->fields()) { + 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 { + CheckFieldFn checkField = getCheckFieldFn(mgr.getASTContext()); + if (!checkField) + return; // skip this target + + 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, 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, 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)); + } + } + } + } + } +} + +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 84886b93d2e4..993f4b0fd61e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -24,6 +24,13 @@ add_clang_library(clangStaticAnalyzerCheckers CheckSecuritySyntaxOnly.cpp CheckSizeofPointer.cpp CheckerDocumentation.cpp + CHERI/AllocationChecker.cpp + CHERI/CapabilityCopyChecker.cpp + CHERI/CheriAPIModelling.cpp + CHERI/CHERIUtils.cpp + CHERI/PointerSizeAssumptionsChecker.cpp + CHERI/ProvenanceSourceChecker.cpp + CHERI/SubObjectRepresentabilityChecker.cpp ChrootChecker.cpp CloneChecker.cpp ContainerModeling.cpp @@ -89,6 +96,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/PointerAlignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp new file mode 100644 index 000000000000..d6533a0b2769 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/PointerAlignmentChecker.cpp @@ -0,0 +1,1105 @@ +//=== 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. +// 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 "CHERI/CHERIUtils.h" +#include +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include +#include +#include +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; +using namespace cheri; +using llvm::APSInt; + +namespace { +class PointerAlignmentChecker + : public Checker, check::PostStmt, + check::PostStmt, check::Bind, + check::PreCall, check::DeadSymbols> { + 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}}, + {{"mempcpy", 3}, {0, 1}}, + {{"memmove", 3}, {0, 1}}, + {{"bcopy", 3}, {1, 0}}, + }; + +public: + 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 *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: + 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; + }; + + 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 + +REGISTER_MAP_WITH_PROGRAMSTATE(TrailingZerosMap, SymbolRef, int) +REGISTER_SET_WITH_PROGRAMSTATE(CapStorageSet, const MemRegion*) + +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 (!isGlobalOrTopLevelArg(BaseRegOrigin->getMemorySpace())) + 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); + +int getTrailingZerosCount(SymbolRef Sym, ProgramStateRef State, + ASTContext &ASTCtx) { + const int *Align = State->get(Sym); + if (Align) + return *Align; + + // Is function argument or global? + 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 APSInt::getUnsigned(A).countTrailingZeros(); + } + + return -1; +} + +int getTrailingZerosCount(const MemRegion *R, ProgramStateRef State, + ASTContext &ASTCtx) { + R = R->StripCasts(); + + 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()) { + 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; + + 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; + + return std::min(BaseTZC, std::max(IdxTZC, 0) + ElTyTZ); + } + + unsigned AlignAttrVal = 0; + if (const 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 APSInt::getUnsigned(A).countTrailingZeros(); + } + return -1; +} + +int getTrailingZerosCount(const SVal &V, ProgramStateRef State, + ASTContext &ASTCtx) { + 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, State, ASTCtx); + if (Res >=0) + return Res; + } + + if (const MemRegion *R = V.getAsRegion()) { + return getTrailingZerosCount(R, State, ASTCtx); + } + + return -1; +} + +int getTrailingZerosCount(const Expr *E, CheckerContext &C) { + const SVal &V = C.getSVal(E); + return getTrailingZerosCount(V, C.getState(), C.getASTContext()); +} + +bool isCapabilityStorage(SymbolRef Sym, ASTContext &ASTCtx) { + const QualType &SymTy = Sym->getType(); + if (SymTy->isPointerType()) { + const QualType &PT = SymTy->getPointeeType(); + if (!PT->isIncompleteType()) { + return hasCapability(PT, 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(); + 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; + 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(); +} + +bool isGenericStorage(SymbolRef Sym, QualType CopyTy) { + if (!isGenericPointerType(CopyTy, false)) + return false; + if (const MemRegion *R = Sym->getOriginRegion()) { + if (isGlobalOrTopLevelArg(R->getMemorySpace())) + return true; // global variable or top-level function 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; + if (SymbolRef Sym = C.getSVal(E).getAsSymbol()) { + return isGenericStorage(Sym, Sym->getType()); + } + return false; +} + +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 << "'"; + } +} + +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->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 + +void PointerAlignmentChecker::checkPreStmt(const CastExpr *CE, + CheckerContext &C) const { + CastKind CK = CE->getCastKind(); + if (CK != CastKind::CK_BitCast && CK != CK_IntegralToPointer) + return; + if (isImplicitConversionFromVoidPtr(CE, C)) + return; + + 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); + if (!DstReqAlign.hasValue()) + return; + + /* Calculate actual alignment */ + const SVal &SrcVal = C.getSVal(CE->getSubExpr()); + 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; + + 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; + QualType DstCapStorageTy = DstTy; + + if (DstTy->isPointerType() && hasCapability(DstTy->getPointeeType(), ASTCtx)) + DstIsPtr2CapStorage = true; + + const ValueDecl *CapDstDecl = nullptr; + const MemRegion *CapStorageReg = L.getAsRegion(); + if (CapStorageReg) { + if (const TypedValueRegion *TR = CapStorageReg->getAs()) { + const QualType &Ty = TR->getValueType(); + 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 = getAllocationDecl(CapStorageReg)) + CapDstDecl = D->getDecl(); + } + + if (SymbolRef Sym = L.getAsSymbol()) { + const QualType &SymTy = Sym->getType(); + if (SymTy->isPointerType()) { + const QualType &CopyTy = SymTy->getPointeeType(); + if (!DstIsPtr2CapStorage && CopyTy->isPointerType() && + hasCapability(CopyTy->getPointeeType(), ASTCtx)) { + DstIsPtr2CapStorage = true; + DstCapStorageTy = CopyTy; + } + DstIsPtr2GenStorage |= isGenericStorage(Sym, CopyTy); + } + } + + if (!DstIsPtr2CapStorage && !DstIsPtr2GenStorage) + return; + + /* Source alignment */ + unsigned CapAlign = getCapabilityTypeAlign(ASTCtx).getQuantity(); + const Optional &SrcAlign = getActualAlignment(C, V); + if (!SrcAlign.hasValue() || SrcAlign >= CapAlign) + return; + + 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 + // the initial value of this region + if (!DerefSym || !DerefSym->getOriginRegion() || + 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 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"; + + 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, + DstIsPtr2CapStorage ? CapStorageAlignBug : GenPtrEscapeAlignBug, + ErrorMessage, CapStorageReg, CapDstDecl, Note); + } else + emitAlignmentWarning(C, V, + DstIsPtr2CapStorage ? CapStorageAlignBug : GenPtrEscapeAlignBug, + ErrorMessage, CapStorageReg); +} + +void PointerAlignmentChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + ASTContext &ASTCtx = C.getASTContext(); + if (!isPureCapMode(ASTCtx)) + return; + + if (!Call.isGlobalCFunction()) + return; + + const std::pair *MemCpyParamPair = MemCpyFn.lookup(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 */ + 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 MemRegion *CapStorageReg = SrcVal.getAsRegion(); + const ValueDecl *CapSrcDecl = nullptr; + if (CapStorageReg) { + if (const DeclRegion *D = getAllocationDecl(CapStorageReg)) + CapSrcDecl = D->getDecl(); + } + + 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, + CapStorageReg, CapSrcDecl, Note); + } else + emitAlignmentWarning(C, DstVal, MemcpyAlignBug, ErrorMessage, + CapStorageReg); + 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 be"; + else + 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 (CapStorageReg) { + if (const DeclRegion *D = getAllocationDecl(CapStorageReg)) + CapDstDecl = D->getDecl(); + } + + 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, + CapStorageReg, CapDstDecl, Note); + } else + emitAlignmentWarning(C, SrcVal, MemcpyAlignBug, ErrorMessage, + CapStorageReg); + return; + } + + /* Propagate CapStorage flag */ + if (SrcIsCapStorage && !DstIsCapStorage) { + if (const MemRegion *R = DstVal.getAsRegion()) { + C.addTransition(C.getState()->add(R->StripCasts())); + } + } + + if (!SrcIsCapStorage && DstIsCapStorage) { + if (const MemRegion *R = SrcVal.getAsRegion()) { + C.addTransition(C.getState()->add(R->StripCasts())); + } + } +} + +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; +} + +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, + CheckerContext &C) const { + CastKind CK = CE->getCastKind(); + if (CK != CastKind::CK_BitCast && CK != CK_PointerToIntegral && + CK != CK_IntegralToPointer) + return; + + int DstTZC = getTrailingZerosCount(CE, C); + int SrcTZC = getTrailingZerosCount(CE->getSubExpr(), C); + + ASTContext &ASTCtx = C.getASTContext(); + bool DstIsCapStorage = false; + if (CE->getType()->isPointerType()) { + if (!isGenericPointerType(CE->getType(), true)) { + const QualType &DstPTy = CE->getType()->getPointeeType(); + if (!DstPTy->isIncompleteType()) { + DstIsCapStorage = hasCapability(DstPTy, ASTCtx); + } + } + } + + SVal DstVal = C.getSVal(CE); + SVal SrcVal = C.getSVal(CE->getSubExpr()); + ProgramStateRef State = C.getState(); + bool Updated = false; + + /* Update TrailingZerosMap */ + if (DstTZC < SrcTZC) { + 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); + Updated = true; + } + } + + /* Update CapStorageSet */ + const ProgramPointTag *Tag = nullptr; + 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 (Updated) + C.addTransition(State, Tag); +} + +void PointerAlignmentChecker::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() && !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; + BinaryOperator::Opcode OpCode = BO->getOpcode(); + switch (OpCode) { + case clang::BO_And: + case clang::BO_AndAssign: + 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: + Res = std::min(LeftTZ, RightTZ); + break; + 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 = ASTCtx.getTypeAlignInChars(PointeeTy); + RightTZ += 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 (auto 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; + + 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); + C.addTransition(State); +} + +void PointerAlignmentChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + bool Updated = false; + + State = cleanDead(State, SymReaper, Updated); + State = cleanDead(State, SymReaper, Updated); + + if (Updated) + C.addTransition(State); +} + +namespace { + +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 ValueDecl *SrcDecl, + PathDiagnosticLocation SrcLoc, + PathSensitiveBugReport &W, ASTContext &ASTCtx, + unsigned FragileAlign) { + SmallString<350> Note; + llvm::raw_svector_ostream OS2(Note); + const QualType &AllocType = SrcDecl->getType().getCanonicalType(); + OS2 << "Original allocation of type "; + OS2 << "'" << AllocType.getAsString() << "'"; + OS2 << " has an alignment requirement "; + unsigned Align = ASTCtx.getTypeAlignInChars(AllocType).getQuantity(); + if (Align == 1) + OS2 << "1 byte"; + else + OS2 << Align << " bytes"; + if (FragileAlign > Align) + OS2 << " (fragile alignment " << FragileAlign << " bytes)"; + W.addNote(Note, SrcLoc); +} + +} // namespace + +ExplodedNode * +PointerAlignmentChecker::emitAlignmentWarning( + CheckerContext &C, + const SVal &UnderalignedPtrVal, + const BugType &BT, + StringRef ErrorMessage, + const MemRegion *CapStorageReg, + const ValueDecl *CapStorageDecl, StringRef CapStorageMsg) const { + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); + if (!ErrNode) + return nullptr; + + 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 = getAllocationDecl(MR)) { + MRDecl = OriginalAlloc->getDecl(); + MRDeclLoc = PathDiagnosticLocation::create(MRDecl, C.getSourceManager()); + } + } + + auto W = std::make_unique(BT, ErrorMessage, ErrNode, + MRDeclLoc, MRDecl); + + W->markInteresting(UnderalignedPtrVal); + if (SymbolRef S = UnderalignedPtrVal.getAsSymbol()) + W->addVisitor(std::make_unique(S)); + + if (MRDecl) { + describeOriginalAllocation(MRDecl, MRDeclLoc, *W, C.getASTContext(), + FragileAlignment); + } + + if (CapStorageDecl) { + auto CapSrcDeclLoc = + PathDiagnosticLocation::create(CapStorageDecl, C.getSourceManager()); + W->addNote(CapStorageMsg, CapSrcDeclLoc); + } + + if (CapStorageReg) { + W->addVisitor(std::make_unique(CapStorageReg)); + } + + C.emitReport(std::move(W)); + return ErrNode; +} + +PathDiagnosticPieceRef PointerAlignmentChecker::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); + + if (Sym != N->getSVal(S).getAsSymbol()) + return nullptr; + + ProgramStateRef State = N->getState(); + 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 (SymbolRef SrcSym = N->getSVal(CE->getSubExpr()).getAsSymbol()) + if (const int *SrcAlign = State->get(SrcSym)) + if (*SrcAlign == *NewAlign) + return nullptr; + } + + OS << "Alignment: "; + if (const BinaryOperator *BO = dyn_cast(E)) { + BinaryOperatorKind const OpCode = BO->getOpcode(); + 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, RTZ); + OS << " => "; + } + } + } + printAlign(OS, *NewAlign); + + // Generate the extra diagnostic. + PathDiagnosticLocation const Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + 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(); +} + +bool ento::shouldRegisterPointerAlignmentChecker(const CheckerManager &Mgr) { + return true; +} \ No newline at end of file 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 = 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"; 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); } 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) { 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; diff --git a/clang/test/Analysis/Checkers/CHERI/allocation.c b/clang/test/Analysis/Checkers/CHERI/allocation.c new file mode 100644 index 000000000000..ef83bd9c0f07 --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/allocation.c @@ -0,0 +1,90 @@ +// RUN: %cheri_purecap_cc1 -analyze -verify %s \ +// RUN: -analyzer-checker=core,unix,alpha.cheri.Allocation,cheri.CheriAPIModelling + +typedef __typeof__(sizeof(int)) size_t; +extern void * malloc(size_t); +void foo(void*); + +struct S1 { + int *a[3]; + int *d[3]; +}; + +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); + return p2; // expected-warning{{Pointer to suballocation returned from function}} +} + +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); + foo(p2); // expected-warning{{Pointer to suballocation passed to function}} +} + +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); + 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]; + foo(p2); // no warn +} + +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); + return p2; // no warn +} + +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); + 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 +} + +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 +} 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; +} 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..bab67b584d48 --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy-hybrid.c @@ -0,0 +1,134 @@ +// RUN: %cheri_cc1 -analyze -verify %s \ +// RUN: -analyzer-checker=core,cheri.CapabilityCopy + +// Don't emit any warnings in 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); + +typedef unsigned long uintptr_t; + +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; +} + +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)) || + ((uintptr_t)src & (BLOCK_SIZE - 1)) || + ((uintptr_t)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 *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 new file mode 100644 index 000000000000..3887992c6dce --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/capability-copy-purecap.c @@ -0,0 +1,212 @@ +// RUN: %cheri_purecap_cc1 -analyze -verify %s \ +// RUN: -analyzer-checker=core,cheri.CapabilityCopy \ +// RUN: -analyzer-config cheri.CapabilityCopy:ReportForCharPtr=true + +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* +} + +// ===== Pointer to capability passed as void* ==== + +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 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)) || + ((uintptr_t)src & (BLOCK_SIZE - 1)) || + ((uintptr_t)dst & (BLOCK_SIZE - 1))) + while (len--) + *dst++ = *src++; // expected-warning{{Tag-stripping store of a capability}} +} + +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}} +} + +struct S { + int x; + int fill[100]; + int *p; +}; + +void struct_field(void *p) { + int x1; + struct S *ps = p; + ps->p = malloc(10*sizeof(int)); + x1 = ps->x; + *ps->p = x1; // no warning +} + +char fp_malloc(void) { + void *q = malloc(100); + char *s = (char*)q; // no warning + return *s; +} + +char fp_init_str(void) { + char sl[] = "String literal"; // no warning + return sl[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; + char c; + while (src1[i]) + *dst++ = src1[i++]; // no warning + + src2++; + while (*src2 != '.') + *dst++ = *src2++; // no warning + + 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); + for (unsigned 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) { + 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 *px, int *py) { + return memcmp_impl(&px, &py, sizeof(int*)); +} + 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..25dbac755b61 --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/provenance-source.c @@ -0,0 +1,239 @@ +// 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 + +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; + 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}} + + // CHECK-FIXES: intptr_t s = b + (ptrdiff_t)(a); + + 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. 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; + + 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. 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}} +} + +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{{Capability with ambiguous provenance 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. 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}} +} + +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}} + + // CHECK-FIXES: intptr_t s = a | (ptrdiff_t)(b); + + 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}} + + // CHECK-FIXES: intptr_t s = a | (ptrdiff_t)(b); + + 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 used as pointer}} +} + +intptr_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}} + + // CHECK-FIXES: return ((intptr_t)str & (ptrdiff_t)(x)); +} + +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}} + + // CHECK-FIXES: return (sz & (size_t)(~mask)); +} + +char* ptr_diff(char *s1, char *s2) { + intptr_t a = (intptr_t)s1; + intptr_t b = (intptr_t)s2; + intptr_t d = a - b; + 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{{NULL-derived capability used as pointer}} +} + +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. 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) { + 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. 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; + // 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); + // 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; // no warning -- intentional + return (char*)p - (char*)q; +} + +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}} +} + +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; + +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}} + + // 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. 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; +} + +intptr_t bar(int d) { + intptr_t a = d; + intptr_t b = get_ptr(); + + return add(a, b); +} 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}} +}; 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..4b8c6aebb3f4 --- /dev/null +++ b/clang/test/Analysis/Checkers/CHERI/subobject-representability-morello.c @@ -0,0 +1,35 @@ +// 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,cheri.SubObjectRepresentability -verify %s + +struct R1 { + struct { + struct { + char c; + char a[0x3FFF]; // no warn + } f1good; + struct { + 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 { + int c[2]; + char a[0x4000]; // 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[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}} +}; diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index e06a8ae5604f..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 @@ -33,6 +34,9 @@ // CHECK-NEXT: cfg-rich-constructors = true // CHECK-NEXT: cfg-scopes = false // CHECK-NEXT: cfg-temporary-dtors = 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 // CHECK-NEXT: core.CallAndMessage:ArgInitializedness = true // CHECK-NEXT: core.CallAndMessage:ArgPointeeInitializedness = false diff --git a/clang/test/Analysis/pointer-alignment.c b/clang/test/Analysis/pointer-alignment.c new file mode 100644 index 000000000000..9df8dec56b24 --- /dev/null +++ b/clang/test/Analysis/pointer-alignment.c @@ -0,0 +1,204 @@ +// RUN: %cheri_purecap_cc1 -analyze -verify %s \ +// RUN: -analyzer-checker=core,optin.portability.PointerAlignment + +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; + +#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, 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 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 16-byte capability alignment}} + + *(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 16-byte capability alignment}} + + 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; +} + +struct S { + intptr_t u[40]; + 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 + 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); +} + +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 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 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 4-byte alignment}} + int b2 = (ip1 == (void*)w); // no-warn + 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}} +} 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 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 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 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]'}} +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 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) { + 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], a5[100], a6[100]; // expected-note{{Original allocation}} expected-note{{}} expected-note{{}} expected-note{{}} expected-note{{}} + +struct T { + void *pVoid; + intptr_t *pCap; +} *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->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->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(a6, m, n); // no-warn + copy(a6, m, n); + + + int x; + memcpy(&x, p, sizeof(int)); // no warn +} + +// ---- +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 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) { + 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->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; +} + +// ---- +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; +} 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,