-
Notifications
You must be signed in to change notification settings - Fork 15.5k
[Clang] Add pointer field protection feature. #172119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: users/pcc/spr/main.clang-add-pointer-field-protection-feature
Are you sure you want to change the base?
[Clang] Add pointer field protection feature. #172119
Conversation
Created using spr 1.3.6-beta.1
|
@llvm/pr-subscribers-clang-codegen @llvm/pr-subscribers-clang Author: Peter Collingbourne (pcc) ChangesPointer field protection is a use-after-free vulnerability Patch is 92.42 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/172119.diff 40 Files Affected:
diff --git a/clang/docs/StructureProtection.rst b/clang/docs/StructureProtection.rst
new file mode 100644
index 0000000000000..5acbdd322cd0a
--- /dev/null
+++ b/clang/docs/StructureProtection.rst
@@ -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.
diff --git a/clang/docs/index.rst b/clang/docs/index.rst
index 70c8737a2fe0d..2a0dc16b5f2a1 100644
--- a/clang/docs/index.rst
+++ b/clang/docs/index.rst
@@ -49,6 +49,7 @@ Using Clang as a Compiler
PointerAuthentication
SafeStack
ShadowCallStack
+ StructureProtection
SourceBasedCodeCoverage
StandardCPlusPlusModules
Modules
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 05926fbd5bd71..ea85a7a4be51c 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -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> {
@@ -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.
diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index 6620840df0ced..7e6e2147a448d 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -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
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 5c4ad3c45da19..15dda098bad47 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -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 {
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 0b006142cbb74..fd3e186c95738 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -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>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 381d1fb063eba..256dab9d2fc22 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -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<
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 8cba1dbaee24e..ef8632dcc2dd0 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -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")
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 45c5322351a17..f2a448c26743b 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -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.">;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index e30681e86b1f0..b4b53b35c16b7 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -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);
+}
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index c16b1bb7a3453..37bc61ca35c4b 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -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());
@@ -456,6 +456,9 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
if (!BaseClassDecl->allowConstDefaultInit())
data().HasUninitializedFields = true;
+ if (BaseClassDecl->isPFPType())
+ data().IsPFPType = true;
+
addedClassSubobject(BaseClassDecl);
}
@@ -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.
@@ -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 {
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d81496ffd74e0..9713e2467eb20 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -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)
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index d2881d5ac518a..a3b4f948d1efe 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -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 << "))";
}
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 01be374422d93..8892006394215 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4521,18 +4521,50 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
Address Dest = EmitPointerWithAlignment(E->getArg(0));
Address Src = EmitPointerWithAlignment(E->getArg(1));
Value *SizeVal = EmitScalarExpr(E->getArg(2));
+ Value *TypeSize = ConstantInt::get(
+ SizeVal->getType(),
+ getContext()
+ .getTypeSizeInChars(E->getArg(0)->getType()->getPointeeType())
+ .getQuantity());
if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate)
- SizeVal = Builder.CreateMul(
- SizeVal,
- ConstantInt::get(
- SizeVal->getType(),
- getContext()
- .getTypeSizeInChars(E->getArg(0)->getType()->getPointeeType())
- .getQuantity()));
+ SizeVal = Builder.CreateMul(SizeVal, TypeSize);
EmitArgCheck(TCK_Store, Dest, E->getArg(0), 0);
EmitArgCheck(TCK_Load, Src, E->getArg(1), 1);
auto *I = Builder.CreateMemMove(Dest, Src, SizeVal, false);
addInstToNewSourceAtom(I, nullptr);
+ if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate) {
+ if (getContext().hasPFPFields(
+ E->getArg(0)->getType()->getPointeeType())) {
+ // Call emitPFPTrivialRelocation for every object in the array we are
+ // relocating.
+ BasicBlock *Entry = Builder.GetInsertBlock();
+ BasicBlock *Loop = createBasicB...
[truncated]
|
|
@llvm/pr-subscribers-clang-driver Author: Peter Collingbourne (pcc) ChangesPointer field protection is a use-after-free vulnerability Patch is 92.42 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/172119.diff 40 Files Affected:
diff --git a/clang/docs/StructureProtection.rst b/clang/docs/StructureProtection.rst
new file mode 100644
index 0000000000000..5acbdd322cd0a
--- /dev/null
+++ b/clang/docs/StructureProtection.rst
@@ -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.
diff --git a/clang/docs/index.rst b/clang/docs/index.rst
index 70c8737a2fe0d..2a0dc16b5f2a1 100644
--- a/clang/docs/index.rst
+++ b/clang/docs/index.rst
@@ -49,6 +49,7 @@ Using Clang as a Compiler
PointerAuthentication
SafeStack
ShadowCallStack
+ StructureProtection
SourceBasedCodeCoverage
StandardCPlusPlusModules
Modules
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 05926fbd5bd71..ea85a7a4be51c 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -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> {
@@ -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.
diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index 6620840df0ced..7e6e2147a448d 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -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
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index 5c4ad3c45da19..15dda098bad47 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -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 {
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 0b006142cbb74..fd3e186c95738 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -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>;
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 381d1fb063eba..256dab9d2fc22 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -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<
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 8cba1dbaee24e..ef8632dcc2dd0 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -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")
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 45c5322351a17..f2a448c26743b 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -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.">;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index e30681e86b1f0..b4b53b35c16b7 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -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);
+}
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index c16b1bb7a3453..37bc61ca35c4b 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -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());
@@ -456,6 +456,9 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
if (!BaseClassDecl->allowConstDefaultInit())
data().HasUninitializedFields = true;
+ if (BaseClassDecl->isPFPType())
+ data().IsPFPType = true;
+
addedClassSubobject(BaseClassDecl);
}
@@ -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.
@@ -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 {
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d81496ffd74e0..9713e2467eb20 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -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)
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index d2881d5ac518a..a3b4f948d1efe 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -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 << "))";
}
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 01be374422d93..8892006394215 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4521,18 +4521,50 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
Address Dest = EmitPointerWithAlignment(E->getArg(0));
Address Src = EmitPointerWithAlignment(E->getArg(1));
Value *SizeVal = EmitScalarExpr(E->getArg(2));
+ Value *TypeSize = ConstantInt::get(
+ SizeVal->getType(),
+ getContext()
+ .getTypeSizeInChars(E->getArg(0)->getType()->getPointeeType())
+ .getQuantity());
if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate)
- SizeVal = Builder.CreateMul(
- SizeVal,
- ConstantInt::get(
- SizeVal->getType(),
- getContext()
- .getTypeSizeInChars(E->getArg(0)->getType()->getPointeeType())
- .getQuantity()));
+ SizeVal = Builder.CreateMul(SizeVal, TypeSize);
EmitArgCheck(TCK_Store, Dest, E->getArg(0), 0);
EmitArgCheck(TCK_Load, Src, E->getArg(1), 1);
auto *I = Builder.CreateMemMove(Dest, Src, SizeVal, false);
addInstToNewSourceAtom(I, nullptr);
+ if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate) {
+ if (getContext().hasPFPFields(
+ E->getArg(0)->getType()->getPointeeType())) {
+ // Call emitPFPTrivialRelocation for every object in the array we are
+ // relocating.
+ BasicBlock *Entry = Builder.GetInsertBlock();
+ BasicBlock *Loop = createBasicB...
[truncated]
|
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
|
(Continuation of #133538.) |
Pointer field protection is a use-after-free vulnerability
mitigation that works by changing how data structures' pointer
fields are stored in memory. For more information, see the RFC:
https://discourse.llvm.org/t/rfc-structure-protection-a-family-of-uaf-mitigation-techniques/85555