Skip to content

Commit 9753ce0

Browse files
committed
Add pointer field protection feature.
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 TODO: - Add tests for coercion. Pull Request: llvm#133538
1 parent 3c6f9e0 commit 9753ce0

33 files changed

+685
-30
lines changed

clang/docs/StructureProtection.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
====================
2+
Structure Protection
3+
====================
4+
5+
.. contents::
6+
:local:
7+
8+
9+
Introduction
10+
============
11+
12+
Structure protection is an experimental mitigation against use-after-free
13+
vulnerabilities. For more details, please see the original `RFC
14+
<https://discourse.llvm.org/t/rfc-structure-protection-a-family-of-uaf-mitigation-techniques/85555>`_.
15+
An independent set of documentation will be added here when the feature
16+
is promoted to non-experimental.
17+
18+
Usage
19+
=====
20+
21+
To use structure protection, build your program using one of the flags:
22+
23+
- ``-fexperimental-pointer-field-protection=untagged``: Enable pointer
24+
field protection with untagged pointers.
25+
26+
- ``-fexperimental-pointer-field-protection=tagged``: Enable pointer
27+
field protection with heap pointers assumed to be tagged by the allocator:
28+
29+
The entire C++ part of the program must be built with a consistent
30+
``-fexperimental-pointer-field-protection`` flag, and the C++ standard
31+
library must also be built with the same flag and statically linked into
32+
the program.

clang/include/clang/AST/ASTContext.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ struct TypeInfoChars {
183183
}
184184
};
185185

186+
struct PFPField {
187+
CharUnits offset;
188+
FieldDecl *field;
189+
};
190+
186191
/// Holds long-lived AST nodes (such as types and decls) that can be
187192
/// referred to throughout the semantic analysis of a file.
188193
class ASTContext : public RefCountedBase<ASTContext> {
@@ -3720,6 +3725,22 @@ OPT_LIST(V)
37203725

37213726
StringRef getCUIDHash() const;
37223727

3728+
bool isPFPStruct(const RecordDecl *rec) const;
3729+
void findPFPFields(QualType Ty, CharUnits Offset,
3730+
std::vector<PFPField> &Fields, bool IncludeVBases) const;
3731+
bool hasPFPFields(QualType ty) const;
3732+
bool isPFPField(const FieldDecl *field) const;
3733+
3734+
/// Returns whether this record's PFP fields (if any) are trivially
3735+
/// relocatable (i.e. may be memcpy'd). This may also return true if the
3736+
/// record does not have any PFP fields, so it may be necessary for the caller
3737+
/// to check for PFP fields, e.g. by calling hasPFPFields().
3738+
bool arePFPFieldsTriviallyRelocatable(const RecordDecl *RD) const;
3739+
3740+
llvm::SetVector<const FieldDecl *> PFPFieldsWithEvaluatedOffset;
3741+
void recordMemberDataPointerEvaluation(const ValueDecl *VD);
3742+
void recordOffsetOfEvaluation(const OffsetOfExpr *E);
3743+
37233744
private:
37243745
/// All OMPTraitInfo objects live in this collection, one per
37253746
/// `pragma omp [begin] declare variant` directive.

clang/include/clang/Basic/Attr.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2597,6 +2597,12 @@ def CountedByOrNull : DeclOrTypeAttr {
25972597
let LangOpts = [COnly];
25982598
}
25992599

2600+
def NoPointerFieldProtection : DeclOrTypeAttr {
2601+
let Spellings = [Clang<"no_field_protection">];
2602+
let Subjects = SubjectList<[Field], ErrorDiag>;
2603+
let Documentation = [Undocumented];
2604+
}
2605+
26002606
def SizedBy : DeclOrTypeAttr {
26012607
let Spellings = [Clang<"sized_by">];
26022608
let Subjects = SubjectList<[Field], ErrorDiag>;

clang/include/clang/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,9 @@ FEATURE(shadow_call_stack,
312312
FEATURE(tls, PP.getTargetInfo().isTLSSupported())
313313
FEATURE(underlying_type, LangOpts.CPlusPlus)
314314
FEATURE(experimental_library, LangOpts.ExperimentalLibrary)
315+
FEATURE(pointer_field_protection,
316+
LangOpts.getPointerFieldProtection() !=
317+
LangOptions::PointerFieldProtectionKind::None)
315318

316319
// C11 features supported by other languages as extensions.
317320
EXTENSION(c_alignas, true)

clang/include/clang/Basic/LangOptions.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,9 @@ LANGOPT(RelativeCXXABIVTables, 1, 0, NotCompatible,
456456
LANGOPT(OmitVTableRTTI, 1, 0, NotCompatible,
457457
"Use an ABI-incompatible v-table layout that omits the RTTI component")
458458

459+
ENUM_LANGOPT(PointerFieldProtection, PointerFieldProtectionKind, 2, PointerFieldProtectionKind::None, NotCompatible,
460+
"Encode struct pointer fields to protect against UAF vulnerabilities")
461+
459462
LANGOPT(VScaleMin, 32, 0, NotCompatible, "Minimum vscale value")
460463
LANGOPT(VScaleMax, 32, 0, NotCompatible, "Maximum vscale value")
461464

clang/include/clang/Basic/LangOptions.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,17 @@ class LangOptionsBase {
376376
BKey
377377
};
378378

379+
enum class PointerFieldProtectionKind {
380+
/// Pointer field protection disabled
381+
None,
382+
/// Pointer field protection enabled, allocator does not tag heap
383+
/// allocations.
384+
Untagged,
385+
/// Pointer field protection enabled, allocator is expected to tag heap
386+
/// allocations.
387+
Tagged,
388+
};
389+
379390
enum class ThreadModelKind {
380391
/// POSIX Threads.
381392
POSIX,

clang/include/clang/Driver/Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3051,6 +3051,12 @@ defm experimental_omit_vtable_rtti : BoolFOption<"experimental-omit-vtable-rtti"
30513051
NegFlag<SetFalse, [], [CC1Option], "Do not omit">,
30523052
BothFlags<[], [CC1Option], " the RTTI component from virtual tables">>;
30533053

3054+
def experimental_pointer_field_protection_EQ : Joined<["-"], "fexperimental-pointer-field-protection=">, Group<f_Group>,
3055+
Visibility<[ClangOption, CC1Option]>,
3056+
Values<"none,untagged,tagged">, NormalizedValuesScope<"LangOptions::PointerFieldProtectionKind">,
3057+
NormalizedValues<["None", "Untagged", "Tagged"]>,
3058+
MarshallingInfoEnum<LangOpts<"PointerFieldProtection">, "None">;
3059+
30543060
def fcxx_abi_EQ : Joined<["-"], "fc++-abi=">,
30553061
Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
30563062
HelpText<"C++ ABI to use. This will override the target C++ ABI.">;

clang/lib/AST/ASTContext.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15181,3 +15181,97 @@ bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl,
1518115181
ThunksToBeAbbreviated[VirtualMethodDecl] = std::move(SimplifiedThunkNames);
1518215182
return Result;
1518315183
}
15184+
15185+
bool ASTContext::arePFPFieldsTriviallyRelocatable(const RecordDecl *RD) const {
15186+
if (getLangOpts().getPointerFieldProtection() ==
15187+
LangOptions::PointerFieldProtectionKind::Tagged)
15188+
return !isa<CXXRecordDecl>(RD) ||
15189+
cast<CXXRecordDecl>(RD)->hasTrivialDestructor();
15190+
return true;
15191+
}
15192+
15193+
bool ASTContext::isPFPStruct(const RecordDecl *rec) const {
15194+
if (getLangOpts().getPointerFieldProtection() !=
15195+
LangOptions::PointerFieldProtectionKind::None)
15196+
if (auto *cxxRec = dyn_cast<CXXRecordDecl>(rec))
15197+
return !cxxRec->isStandardLayout();
15198+
return false;
15199+
}
15200+
15201+
void ASTContext::findPFPFields(QualType Ty, CharUnits Offset,
15202+
std::vector<PFPField> &Fields,
15203+
bool IncludeVBases) const {
15204+
if (auto *AT = getAsConstantArrayType(Ty)) {
15205+
if (auto *ElemDecl = AT->getElementType()->getAsCXXRecordDecl()) {
15206+
const ASTRecordLayout &ElemRL = getASTRecordLayout(ElemDecl);
15207+
for (unsigned i = 0; i != AT->getSize(); ++i) {
15208+
findPFPFields(AT->getElementType(), Offset + i * ElemRL.getSize(),
15209+
Fields, true);
15210+
}
15211+
}
15212+
}
15213+
auto *Decl = Ty->getAsCXXRecordDecl();
15214+
if (!Decl)
15215+
return;
15216+
const ASTRecordLayout &RL = getASTRecordLayout(Decl);
15217+
for (FieldDecl *field : Decl->fields()) {
15218+
CharUnits fieldOffset =
15219+
Offset + toCharUnitsFromBits(RL.getFieldOffset(field->getFieldIndex()));
15220+
if (isPFPField(field))
15221+
Fields.push_back({fieldOffset, field});
15222+
findPFPFields(field->getType(), fieldOffset, Fields, true);
15223+
}
15224+
for (auto &Base : Decl->bases()) {
15225+
if (Base.isVirtual())
15226+
continue;
15227+
CharUnits BaseOffset =
15228+
Offset + RL.getBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
15229+
findPFPFields(Base.getType(), BaseOffset, Fields, false);
15230+
}
15231+
if (IncludeVBases) {
15232+
for (auto &Base : Decl->vbases()) {
15233+
CharUnits BaseOffset =
15234+
Offset + RL.getVBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
15235+
findPFPFields(Base.getType(), BaseOffset, Fields, false);
15236+
}
15237+
}
15238+
}
15239+
15240+
bool ASTContext::hasPFPFields(QualType ty) const {
15241+
std::vector<PFPField> pfpFields;
15242+
findPFPFields(ty, CharUnits::Zero(), pfpFields, true);
15243+
return !pfpFields.empty();
15244+
}
15245+
15246+
bool ASTContext::isPFPField(const FieldDecl *field) const {
15247+
if (!isPFPStruct(field->getParent()))
15248+
return false;
15249+
return field->getType()->isPointerType() &&
15250+
!field->hasAttr<NoPointerFieldProtectionAttr>();
15251+
}
15252+
15253+
void ASTContext::recordMemberDataPointerEvaluation(const ValueDecl *VD) {
15254+
if (getLangOpts().getPointerFieldProtection() ==
15255+
LangOptions::PointerFieldProtectionKind::None)
15256+
return;
15257+
auto *FD = dyn_cast<FieldDecl>(VD);
15258+
if (!FD)
15259+
FD = cast<FieldDecl>(cast<IndirectFieldDecl>(VD)->chain().back());
15260+
if (!isPFPField(FD))
15261+
return;
15262+
PFPFieldsWithEvaluatedOffset.insert(FD);
15263+
}
15264+
15265+
void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
15266+
if (getLangOpts().getPointerFieldProtection() ==
15267+
LangOptions::PointerFieldProtectionKind::None ||
15268+
E->getNumComponents() == 0)
15269+
return;
15270+
OffsetOfNode Comp = E->getComponent(E->getNumComponents() - 1);
15271+
if (Comp.getKind() != OffsetOfNode::Field)
15272+
return;
15273+
FieldDecl *FD = Comp.getField();
15274+
if (!isPFPField(FD))
15275+
return;
15276+
PFPFieldsWithEvaluatedOffset.insert(FD);
15277+
}

clang/lib/AST/ExprConstant.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15084,6 +15084,7 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr(
1508415084
}
1508515085

1508615086
bool IntExprEvaluator::VisitOffsetOfExpr(const OffsetOfExpr *OOE) {
15087+
Info.Ctx.recordOffsetOfEvaluation(OOE);
1508715088
CharUnits Result;
1508815089
unsigned n = OOE->getNumComponents();
1508915090
if (n == 0)

clang/lib/AST/TypePrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2129,6 +2129,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
21292129
case attr::ExtVectorType:
21302130
OS << "ext_vector_type";
21312131
break;
2132+
case attr::NoPointerFieldProtection:
2133+
OS << "no_field_protection";
2134+
break;
21322135
}
21332136
OS << "))";
21342137
}

0 commit comments

Comments
 (0)