Skip to content

Commit 5917e54

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: - Fix test failure. - Add more tests. - Add documentation. Pull Request: llvm#133538
1 parent 23e0789 commit 5917e54

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1111
-82
lines changed

clang/include/clang/AST/ASTContext.h

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

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

37043710
StringRef getCUIDHash() const;
37053711

3712+
bool isPFPStruct(const RecordDecl *rec) const;
3713+
void findPFPFields(QualType Ty, CharUnits Offset,
3714+
std::vector<PFPField> &Fields, bool IncludeVBases) const;
3715+
bool hasPFPFields(QualType ty) const;
3716+
bool isPFPField(const FieldDecl *field) const;
3717+
3718+
/// Returns whether this record's PFP fields (if any) are trivially
3719+
/// relocatable (i.e. may be memcpy'd). This may also return true if the
3720+
/// record does not have any PFP fields, so it may be necessary for the caller
3721+
/// to check for PFP fields, e.g. by calling hasPFPFields().
3722+
bool arePFPFieldsTriviallyRelocatable(const RecordDecl *RD) const;
3723+
3724+
llvm::SetVector<const FieldDecl *> PFPFieldsWithEvaluatedOffset;
3725+
void recordMemberDataPointerEvaluation(const ValueDecl *VD);
3726+
void recordOffsetOfEvaluation(const OffsetOfExpr *E);
3727+
37063728
private:
37073729
/// All OMPTraitInfo objects live in this collection, one per
37083730
/// `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
@@ -265,6 +265,9 @@ FEATURE(shadow_call_stack,
265265
FEATURE(tls, PP.getTargetInfo().isTLSSupported())
266266
FEATURE(underlying_type, LangOpts.CPlusPlus)
267267
FEATURE(experimental_library, LangOpts.ExperimentalLibrary)
268+
FEATURE(pointer_field_protection,
269+
LangOpts.getPointerFieldProtection() !=
270+
LangOptions::PointerFieldProtectionKind::None)
268271

269272
// C11 features supported by other languages as extensions.
270273
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
@@ -379,6 +379,17 @@ class LangOptionsBase {
379379
BKey
380380
};
381381

382+
enum class PointerFieldProtectionKind {
383+
/// Pointer field protection disabled
384+
None,
385+
/// Pointer field protection enabled, allocator does not tag heap
386+
/// allocations.
387+
Untagged,
388+
/// Pointer field protection enabled, allocator is expected to tag heap
389+
/// allocations.
390+
Tagged,
391+
};
392+
382393
enum class ThreadModelKind {
383394
/// POSIX Threads.
384395
POSIX,

clang/include/clang/Driver/Options.td

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

3042+
def experimental_pointer_field_protection_EQ : Joined<["-"], "fexperimental-pointer-field-protection=">, Group<f_Group>,
3043+
Visibility<[ClangOption, CC1Option]>,
3044+
Values<"none,untagged,tagged">, NormalizedValuesScope<"LangOptions::PointerFieldProtectionKind">,
3045+
NormalizedValues<["None", "Untagged", "Tagged"]>,
3046+
MarshallingInfoEnum<LangOpts<"PointerFieldProtection">, "None">;
3047+
30423048
def fcxx_abi_EQ : Joined<["-"], "fc++-abi=">,
30433049
Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
30443050
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
@@ -15162,3 +15162,97 @@ bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl,
1516215162
ThunksToBeAbbreviated[VirtualMethodDecl] = std::move(SimplifiedThunkNames);
1516315163
return Result;
1516415164
}
15165+
15166+
bool ASTContext::arePFPFieldsTriviallyRelocatable(const RecordDecl *RD) const {
15167+
if (getLangOpts().getPointerFieldProtection() ==
15168+
LangOptions::PointerFieldProtectionKind::Tagged)
15169+
return !isa<CXXRecordDecl>(RD) ||
15170+
cast<CXXRecordDecl>(RD)->hasTrivialDestructor();
15171+
return true;
15172+
}
15173+
15174+
bool ASTContext::isPFPStruct(const RecordDecl *rec) const {
15175+
if (getLangOpts().getPointerFieldProtection() !=
15176+
LangOptions::PointerFieldProtectionKind::None)
15177+
if (auto *cxxRec = dyn_cast<CXXRecordDecl>(rec))
15178+
return !cxxRec->isStandardLayout();
15179+
return false;
15180+
}
15181+
15182+
void ASTContext::findPFPFields(QualType Ty, CharUnits Offset,
15183+
std::vector<PFPField> &Fields,
15184+
bool IncludeVBases) const {
15185+
if (auto *AT = getAsConstantArrayType(Ty)) {
15186+
if (auto *ElemDecl = AT->getElementType()->getAsCXXRecordDecl()) {
15187+
const ASTRecordLayout &ElemRL = getASTRecordLayout(ElemDecl);
15188+
for (unsigned i = 0; i != AT->getSize(); ++i) {
15189+
findPFPFields(AT->getElementType(), Offset + i * ElemRL.getSize(),
15190+
Fields, true);
15191+
}
15192+
}
15193+
}
15194+
auto *Decl = Ty->getAsCXXRecordDecl();
15195+
if (!Decl)
15196+
return;
15197+
const ASTRecordLayout &RL = getASTRecordLayout(Decl);
15198+
for (FieldDecl *field : Decl->fields()) {
15199+
CharUnits fieldOffset =
15200+
Offset + toCharUnitsFromBits(RL.getFieldOffset(field->getFieldIndex()));
15201+
if (isPFPField(field))
15202+
Fields.push_back({Offset, fieldOffset, field});
15203+
findPFPFields(field->getType(), fieldOffset, Fields, true);
15204+
}
15205+
for (auto &Base : Decl->bases()) {
15206+
if (Base.isVirtual())
15207+
continue;
15208+
CharUnits BaseOffset =
15209+
Offset + RL.getBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
15210+
findPFPFields(Base.getType(), BaseOffset, Fields, false);
15211+
}
15212+
if (IncludeVBases) {
15213+
for (auto &Base : Decl->vbases()) {
15214+
CharUnits BaseOffset =
15215+
Offset + RL.getVBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
15216+
findPFPFields(Base.getType(), BaseOffset, Fields, false);
15217+
}
15218+
}
15219+
}
15220+
15221+
bool ASTContext::hasPFPFields(QualType ty) const {
15222+
std::vector<PFPField> pfpFields;
15223+
findPFPFields(ty, CharUnits::Zero(), pfpFields, true);
15224+
return !pfpFields.empty();
15225+
}
15226+
15227+
bool ASTContext::isPFPField(const FieldDecl *field) const {
15228+
if (!isPFPStruct(field->getParent()))
15229+
return false;
15230+
return field->getType()->isPointerType() &&
15231+
!field->hasAttr<NoPointerFieldProtectionAttr>();
15232+
}
15233+
15234+
void ASTContext::recordMemberDataPointerEvaluation(const ValueDecl *VD) {
15235+
if (getLangOpts().getPointerFieldProtection() ==
15236+
LangOptions::PointerFieldProtectionKind::None)
15237+
return;
15238+
auto *FD = dyn_cast<FieldDecl>(VD);
15239+
if (!FD)
15240+
FD = cast<FieldDecl>(cast<IndirectFieldDecl>(VD)->chain().back());
15241+
if (!isPFPField(FD))
15242+
return;
15243+
PFPFieldsWithEvaluatedOffset.insert(FD);
15244+
}
15245+
15246+
void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
15247+
if (getLangOpts().getPointerFieldProtection() ==
15248+
LangOptions::PointerFieldProtectionKind::None ||
15249+
E->getNumComponents() == 0)
15250+
return;
15251+
OffsetOfNode Comp = E->getComponent(E->getNumComponents() - 1);
15252+
if (Comp.getKind() != OffsetOfNode::Field)
15253+
return;
15254+
FieldDecl *FD = Comp.getField();
15255+
if (!isPFPField(FD))
15256+
return;
15257+
PFPFieldsWithEvaluatedOffset.insert(FD);
15258+
}

clang/lib/AST/ExprConstant.cpp

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

1500315003
bool IntExprEvaluator::VisitOffsetOfExpr(const OffsetOfExpr *OOE) {
15004+
Info.Ctx.recordOffsetOfEvaluation(OOE);
1500415005
CharUnits Result;
1500515006
unsigned n = OOE->getNumComponents();
1500615007
if (n == 0)

clang/lib/AST/TypePrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2119,6 +2119,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
21192119
case attr::ExtVectorType:
21202120
OS << "ext_vector_type";
21212121
break;
2122+
case attr::NoPointerFieldProtection:
2123+
OS << "no_field_protection";
2124+
break;
21222125
}
21232126
OS << "))";
21242127
}

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4494,6 +4494,19 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
44944494
EmitArgCheck(TCK_Load, Src, E->getArg(1), 1);
44954495
auto *I = Builder.CreateMemMove(Dest, Src, SizeVal, false);
44964496
addInstToNewSourceAtom(I, nullptr);
4497+
if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate) {
4498+
std::vector<PFPField> PFPFields;
4499+
getContext().findPFPFields(E->getArg(0)->getType()->getPointeeType(),
4500+
CharUnits::Zero(), PFPFields, true);
4501+
for (auto &Field : PFPFields) {
4502+
if (getContext().arePFPFieldsTriviallyRelocatable(
4503+
Field.field->getParent()))
4504+
continue;
4505+
auto DestFieldPtr = EmitAddressOfPFPField(Dest, Field);
4506+
auto SrcFieldPtr = EmitAddressOfPFPField(Src, Field);
4507+
Builder.CreateStore(Builder.CreateLoad(SrcFieldPtr), DestFieldPtr);
4508+
}
4509+
}
44974510
return RValue::get(Dest, *this);
44984511
}
44994512
case Builtin::BImemset:

0 commit comments

Comments
 (0)