Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions clang/docs/StructureProtection.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
====================
Structure Protection
====================

.. contents::
:local:


Introduction
============

Structure protection is an *experimental* mitigation
against use-after-free vulnerabilities. For
more information, please see the original `RFC
<https://discourse.llvm.org/t/rfc-structure-protection-a-family-of-uaf-mitigation-techniques/85555>`_.
An independent set of documentation will be contributed when the feature
is promoted to stable.

Usage
=====

To use structure protection, build your program using one or more of these flags:

- ``-fexperimental-allow-pointer-field-protection-attr``: Makes the
``[[clang::pointer_field_protection]]`` attribute described below
available for use in code. Without this flag, use of the attribute will
cause an error. This flag acts as a guard against use of the feature
before it is stabilized. When the feature is stabilized, the intent
is that this flag will become a no-op and the attribute will always
be available.

- ``-fexperimental-pointer-field-protection-abi``: Enable pointer
field protection on all types that are not considered standard-layout
according to the C++ rules for standard layout. Because this
flag changes the C++ ABI, we refer to this as the pointer
field protection ABI. Specifying this flag also defines the
predefined macro ``__POINTER_FIELD_PROTECTION_ABI__``. Implies
``-fexperimental-allow-pointer-field-protection-attr``.

- ``-fexperimental-pointer-field-protection-tagged``: On architectures
that support it (currently only AArch64), for types that are not
considered trivially copyable, use the address of the object to compute
the pointer encoding. Specifying this flag also defines the predefined
macro ``__POINTER_FIELD_PROTECTION_TAGGED__``.

It is also possible to specify the attribute
``[[clang::pointer_field_protection]]`` on a struct type to opt the
struct's pointer fields into pointer field protection, even if the type is
standard layout or none of the command line flags are specified. Note that
this means that the type will not comply with pointer interconvertibility
and other standard layout rules.

Pointer field protection is inherited from bases and non-static data
members.

In order to avoid ABI breakage, the entire C++ part
of the program must be built with a consistent set of
``-fexperimental-pointer-field-protection*`` flags, and the C++ standard
library must also be built with the same flags and statically linked
into the program.

To build libc++ with pointer field protection support, pass the following
CMake flags:

.. code-block:: console

"-DRUNTIMES_${triple}_LIBCXXABI_ENABLE_SHARED=OFF" \
"-DRUNTIMES_${triple}_LIBCXX_USE_COMPILER_RT=ON" \
"-DRUNTIMES_${triple}_LIBCXX_PFP=untagged" \
"-DRUNTIMES_${triple}_LIBCXX_ENABLE_SHARED=OFF" \
"-DRUNTIMES_${triple}_LIBCXX_TEST_CONFIG=llvm-libc++-static.cfg.in" \
"-DRUNTIMES_${triple}_LIBUNWIND_ENABLE_SHARED=OFF" \

where ``${triple}`` is your target triple, such as
``aarch64-unknown-linux``.

The resulting toolchain may then be used to build programs
with pointer field protection by passing ``-stdlib=libc++
-fexperimental-pointer-field-protection-abi`` at compile time
and ``-Wl,-Bstatic -lc++ -lc++abi -Wl,-Bdynamic -lm -fuse-ld=lld
-static-libstdc++`` at link time.
1 change: 1 addition & 0 deletions clang/docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Using Clang as a Compiler
PointerAuthentication
SafeStack
ShadowCallStack
StructureProtection
SourceBasedCodeCoverage
StandardCPlusPlusModules
Modules
Expand Down
22 changes: 22 additions & 0 deletions clang/include/clang/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,12 @@ struct TypeInfoChars {
}
};

struct PFPField {
CharUnits offset;
FieldDecl *field;
bool isWithinUnion;
};

/// Holds long-lived AST nodes (such as types and decls) that can be
/// referred to throughout the semantic analysis of a file.
class ASTContext : public RefCountedBase<ASTContext> {
Expand Down Expand Up @@ -3802,6 +3808,22 @@ OPT_LIST(V)

StringRef getCUIDHash() const;

void findPFPFields(QualType Ty, CharUnits Offset,
std::vector<PFPField> &Fields, bool IncludeVBases,
bool IsWithinUnion = false) const;
bool hasPFPFields(QualType ty) const;
bool isPFPField(const FieldDecl *field) const;

/// Returns whether this record's PFP fields (if any) are trivially
/// copyable (i.e. may be memcpy'd). This may also return true if the
/// record does not have any PFP fields, so it may be necessary for the caller
/// to check for PFP fields, e.g. by calling hasPFPFields().
bool arePFPFieldsTriviallyCopyable(const RecordDecl *RD) const;

llvm::SetVector<const FieldDecl *> PFPFieldsWithEvaluatedOffset;
void recordMemberDataPointerEvaluation(const ValueDecl *VD);
void recordOffsetOfEvaluation(const OffsetOfExpr *E);

private:
/// All OMPTraitInfo objects live in this collection, one per
/// `pragma omp [begin] declare variant` directive.
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,10 @@ FIELD(IsAnyDestructorNoReturn, 1, NO_MERGE)
/// type that is intangible). HLSL only.
FIELD(IsHLSLIntangible, 1, NO_MERGE)

/// Whether the pointer fields in this class should have pointer field
/// protection (PFP) by default, either because of an attribute, the
/// -fexperimental-pointer-field-protection-abi compiler flag or inheritance
/// from a base or member with PFP.
FIELD(IsPFPType, 1, NO_MERGE)

#undef FIELD
6 changes: 6 additions & 0 deletions clang/include/clang/AST/DeclCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,12 @@ class CXXRecordDecl : public RecordDecl {
/// Determine whether this class has any variant members.
bool hasVariantMembers() const { return data().HasVariantMembers; }

/// Returns whether the pointer fields in this class should have pointer field
/// protection (PFP) by default, either because of an attribute, the
/// -fexperimental-pointer-field-protection-abi compiler flag or inheritance
/// from a base or member with PFP.
bool isPFPType() const { return data().IsPFPType; }

/// Determine whether this class has a trivial default constructor
/// (C++11 [class.ctor]p5).
bool hasTrivialDefaultConstructor() const {
Expand Down
13 changes: 13 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -2643,6 +2643,19 @@ def CountedByOrNull : DeclOrTypeAttr {
let LangOpts = [COnly];
}

def NoFieldProtection : DeclOrTypeAttr {
let Spellings = [Clang<"no_field_protection">];
let Subjects = SubjectList<[Field], ErrorDiag>;
let Documentation = [Undocumented];
}

def PointerFieldProtection : DeclOrTypeAttr {
let Spellings = [Clang<"pointer_field_protection">];
let Subjects = SubjectList<[CXXRecord], ErrorDiag>;
let Documentation = [Undocumented];
let SimpleHandler = 1;
}

def SizedBy : DeclOrTypeAttr {
let Spellings = [Clang<"sized_by">];
let Subjects = SubjectList<[Field], ErrorDiag>;
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3995,6 +3995,8 @@ def err_attribute_vecreturn_only_vector_member : Error<
"the vecreturn attribute can only be used on a class or structure with one member, which must be a vector">;
def err_attribute_vecreturn_only_pod_record : Error<
"the vecreturn attribute can only be used on a POD (plain old data) class or structure (i.e. no virtual functions)">;
def err_attribute_pointer_field_protection_experimental: Error<
"this attribute is experimental and must be explicitly enabled with flag -fexperimental-allow-pointer-field-protection-attr">;
def err_sme_attr_mismatch : Error<
"function declared %0 was previously declared %1, which has different SME function attributes">;
def err_sme_call_in_non_sme_target : Error<
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Basic/LangOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,13 @@ LANGOPT(RelativeCXXABIVTables, 1, 0, NotCompatible,
LANGOPT(OmitVTableRTTI, 1, 0, NotCompatible,
"Use an ABI-incompatible v-table layout that omits the RTTI component")

LANGOPT(PointerFieldProtectionAttr, 1, 0, NotCompatible,
"Allow the use of the experimental [[clang::pointer_field_protection]] attribute")
LANGOPT(PointerFieldProtectionABI, 1, 0, NotCompatible,
"Enable pointer field protection by default for all non-standard-layout types")
LANGOPT(PointerFieldProtectionTagged, 1, 0, NotCompatible,
"Use pointer identity (tag) to discriminate pointers of non-trivially-copyable types")

LANGOPT(VScaleMin, 32, 0, NotCompatible, "Minimum vscale value")
LANGOPT(VScaleMax, 32, 0, NotCompatible, "Maximum vscale value")

Expand Down
16 changes: 16 additions & 0 deletions clang/include/clang/Options/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -3178,6 +3178,22 @@ defm experimental_omit_vtable_rtti : BoolFOption<"experimental-omit-vtable-rtti"
NegFlag<SetFalse, [], [CC1Option], "Do not omit">,
BothFlags<[], [CC1Option], " the RTTI component from virtual tables">>;

defm experimental_allow_pointer_field_protection_attr : BoolFOption<"experimental-allow-pointer-field-protection-attr",
LangOpts<"PointerFieldProtectionAttr">, DefaultFalse,
PosFlag<SetTrue, [], [CC1Option], "Allow">,
NegFlag<SetFalse, [], [], "Disallow">,
BothFlags<[], [ClangOption], " the use of the [[clang::pointer_field_protection]] attribute">>;
defm experimental_pointer_field_protection_abi : BoolFOption<"experimental-pointer-field-protection-abi",
LangOpts<"PointerFieldProtectionABI">, DefaultFalse,
PosFlag<SetTrue, [], [CC1Option], "Enable">,
NegFlag<SetFalse, [], [], "Do not enable">,
BothFlags<[], [ClangOption], " pointer field protection on all non-standard layout struct types">>;
defm experimental_pointer_field_protection_tagged : BoolFOption<"experimental-pointer-field-protection-tagged",
LangOpts<"PointerFieldProtectionTagged">, DefaultFalse,
PosFlag<SetTrue, [], [CC1Option], "Use">,
NegFlag<SetFalse, [], [], "Do not use">,
BothFlags<[], [ClangOption], " pointer identity (tag) to discriminate pointers of non-trivially-copyable types">>;

def fcxx_abi_EQ : Joined<["-"], "fc++-abi=">,
Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
HelpText<"C++ ABI to use. This will override the target C++ ABI.">;
Expand Down
88 changes: 88 additions & 0 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15227,3 +15227,91 @@ bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl,
ThunksToBeAbbreviated[VirtualMethodDecl] = std::move(SimplifiedThunkNames);
return Result;
}

bool ASTContext::arePFPFieldsTriviallyCopyable(const RecordDecl *RD) const {
bool IsPAuthSupported =
getTargetInfo().getTriple().getArch() == llvm::Triple::aarch64;
if (!IsPAuthSupported)
return true;
if (getLangOpts().PointerFieldProtectionTagged)
return !isa<CXXRecordDecl>(RD) ||
cast<CXXRecordDecl>(RD)->hasTrivialDestructor();
return true;
}

void ASTContext::findPFPFields(QualType Ty, CharUnits Offset,
std::vector<PFPField> &Fields,
bool IncludeVBases, bool IsWithinUnion) const {
if (auto *AT = getAsConstantArrayType(Ty)) {
if (auto *ElemDecl = AT->getElementType()->getAsCXXRecordDecl()) {
const ASTRecordLayout &ElemRL = getASTRecordLayout(ElemDecl);
for (unsigned i = 0; i != AT->getSize(); ++i)
findPFPFields(AT->getElementType(), Offset + i * ElemRL.getSize(),
Fields, true);
}
}
auto *Decl = Ty->getAsCXXRecordDecl();
// isPFPType() is inherited from bases and members (including via arrays), so
// we can early exit if it is false.
if (!Decl || !Decl->isPFPType())
return;
IsWithinUnion |= Decl->isUnion();
const ASTRecordLayout &RL = getASTRecordLayout(Decl);
for (FieldDecl *field : Decl->fields()) {
CharUnits fieldOffset =
Offset + toCharUnitsFromBits(RL.getFieldOffset(field->getFieldIndex()));
if (isPFPField(field))
Fields.push_back({fieldOffset, field, IsWithinUnion});
findPFPFields(field->getType(), fieldOffset, Fields, /*IncludeVBases=*/true,
IsWithinUnion);
}
// Pass false for IncludeVBases below because vbases are only included in
// layout for top-level types, i.e. not bases or vbases.
for (auto &Base : Decl->bases()) {
if (Base.isVirtual())
continue;
CharUnits BaseOffset =
Offset + RL.getBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
findPFPFields(Base.getType(), BaseOffset, Fields, /*IncludeVBases=*/false,
IsWithinUnion);
}
if (IncludeVBases) {
for (auto &Base : Decl->vbases()) {
CharUnits BaseOffset =
Offset + RL.getVBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
findPFPFields(Base.getType(), BaseOffset, Fields, /*IncludeVBases=*/false,
IsWithinUnion);
}
}
}

bool ASTContext::hasPFPFields(QualType Ty) const {
std::vector<PFPField> PFPFields;
findPFPFields(Ty, CharUnits::Zero(), PFPFields, true);
return !PFPFields.empty();
}

bool ASTContext::isPFPField(const FieldDecl *FD) const {
if (auto *RD = dyn_cast<CXXRecordDecl>(FD->getParent()))
return RD->isPFPType() && FD->getType()->isPointerType() &&
!FD->hasAttr<NoFieldProtectionAttr>();
return false;
}

void ASTContext::recordMemberDataPointerEvaluation(const ValueDecl *VD) {
auto *FD = dyn_cast<FieldDecl>(VD);
if (!FD)
FD = cast<FieldDecl>(cast<IndirectFieldDecl>(VD)->chain().back());
if (isPFPField(FD))
PFPFieldsWithEvaluatedOffset.insert(FD);
}

void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
if (E->getNumComponents() == 0)
return;
OffsetOfNode Comp = E->getComponent(E->getNumComponents() - 1);
if (Comp.getKind() != OffsetOfNode::Field)
return;
if (FieldDecl *FD = Comp.getField(); isPFPField(FD))
PFPFieldsWithEvaluatedOffset.insert(FD);
}
20 changes: 17 additions & 3 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
ImplicitCopyAssignmentHasConstParam(true),
HasDeclaredCopyConstructorWithConstParam(false),
HasDeclaredCopyAssignmentWithConstParam(false),
IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsLambda(false),
IsParsingBaseSpecifiers(false), ComputedVisibleConversions(false),
HasODRHash(false), Definition(D) {}
IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsPFPType(false),
IsLambda(false), IsParsingBaseSpecifiers(false),
ComputedVisibleConversions(false), HasODRHash(false), Definition(D) {}

CXXBaseSpecifier *CXXRecordDecl::DefinitionData::getBasesSlowCase() const {
return Bases.get(Definition->getASTContext().getExternalSource());
Expand Down Expand Up @@ -456,6 +456,9 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
if (!BaseClassDecl->allowConstDefaultInit())
data().HasUninitializedFields = true;

if (BaseClassDecl->isPFPType())
data().IsPFPType = true;

addedClassSubobject(BaseClassDecl);
}

Expand Down Expand Up @@ -1408,6 +1411,9 @@ void CXXRecordDecl::addedMember(Decl *D) {
if (FieldRec->hasVariantMembers() &&
Field->isAnonymousStructOrUnion())
data().HasVariantMembers = true;

if (FieldRec->isPFPType())
data().IsPFPType = true;
}
} else {
// Base element type of field is a non-class type.
Expand Down Expand Up @@ -2305,6 +2311,14 @@ void CXXRecordDecl::completeDefinition(CXXFinalOverriderMap *FinalOverriders) {
}
setHasUninitializedExplicitInitFields(false);
}

if (getLangOpts().PointerFieldProtectionABI && !isStandardLayout()) {
data().IsPFPType = true;
} else if (hasAttr<PointerFieldProtectionAttr>()) {
data().IsPFPType = true;
data().IsStandardLayout = false;
data().IsCXX11StandardLayout = false;
}
}

bool CXXRecordDecl::mayBeAbstract() const {
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18382,6 +18382,7 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr(
}

bool IntExprEvaluator::VisitOffsetOfExpr(const OffsetOfExpr *OOE) {
Info.Ctx.recordOffsetOfEvaluation(OOE);
CharUnits Result;
unsigned n = OOE->getNumComponents();
if (n == 0)
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/AST/TypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2122,6 +2122,12 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::CFISalt:
OS << "cfi_salt(\"" << cast<CFISaltAttr>(T->getAttr())->getSalt() << "\")";
break;
case attr::NoFieldProtection:
OS << "no_field_protection";
break;
case attr::PointerFieldProtection:
OS << "pointer_field_protection";
break;
}
OS << "))";
}
Expand Down
Loading