Skip to content

Commit 36a7963

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 e67962a commit 36a7963

Some content is hidden

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

57 files changed

+1095
-64
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> {
@@ -3668,6 +3674,22 @@ OPT_LIST(V)
36683674

36693675
StringRef getCUIDHash() const;
36703676

3677+
bool isPFPStruct(const RecordDecl *rec) const;
3678+
void findPFPFields(QualType Ty, CharUnits Offset,
3679+
std::vector<PFPField> &Fields, bool IncludeVBases) const;
3680+
bool hasPFPFields(QualType ty) const;
3681+
bool isPFPField(const FieldDecl *field) const;
3682+
3683+
/// Returns whether this record's PFP fields (if any) are trivially
3684+
/// relocatable (i.e. may be memcpy'd). This may also return true if the
3685+
/// record does not have any PFP fields, so it may be necessary for the caller
3686+
/// to check for PFP fields, e.g. by calling hasPFPFields().
3687+
bool arePFPFieldsTriviallyRelocatable(const RecordDecl *RD) const;
3688+
3689+
llvm::SetVector<const FieldDecl *> PFPFieldsWithEvaluatedOffset;
3690+
void recordMemberDataPointerEvaluation(const ValueDecl *VD);
3691+
void recordOffsetOfEvaluation(const OffsetOfExpr *E);
3692+
36713693
private:
36723694
/// All OMPTraitInfo objects live in this collection, one per
36733695
/// `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
@@ -2524,6 +2524,12 @@ def CountedByOrNull : DeclOrTypeAttr {
25242524
let LangOpts = [COnly];
25252525
}
25262526

2527+
def NoPointerFieldProtection : DeclOrTypeAttr {
2528+
let Spellings = [Clang<"no_field_protection">];
2529+
let Subjects = SubjectList<[Field], ErrorDiag>;
2530+
let Documentation = [Undocumented];
2531+
}
2532+
25272533
def SizedBy : DeclOrTypeAttr {
25282534
let Spellings = [Clang<"sized_by">];
25292535
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
@@ -500,6 +500,9 @@ LANGOPT(RelativeCXXABIVTables, 1, 0,
500500
LANGOPT(OmitVTableRTTI, 1, 0,
501501
"Use an ABI-incompatible v-table layout that omits the RTTI component")
502502

503+
ENUM_LANGOPT(PointerFieldProtection, PointerFieldProtectionKind, 2, PointerFieldProtectionKind::None,
504+
"Encode struct pointer fields to protect against UAF vulnerabilities")
505+
503506
LANGOPT(VScaleMin, 32, 0, "Minimum vscale value")
504507
LANGOPT(VScaleMax, 32, 0, "Maximum vscale value")
505508

clang/include/clang/Basic/LangOptions.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,17 @@ class LangOptionsBase {
362362
BKey
363363
};
364364

365+
enum class PointerFieldProtectionKind {
366+
/// Pointer field protection disabled
367+
None,
368+
/// Pointer field protection enabled, allocator does not tag heap
369+
/// allocations.
370+
Untagged,
371+
/// Pointer field protection enabled, allocator is expected to tag heap
372+
/// allocations.
373+
Tagged,
374+
};
375+
365376
enum class ThreadModelKind {
366377
/// POSIX Threads.
367378
POSIX,

clang/include/clang/Driver/Options.td

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

3014+
def experimental_pointer_field_protection_EQ : Joined<["-"], "fexperimental-pointer-field-protection=">, Group<f_Group>,
3015+
Visibility<[ClangOption, CC1Option]>,
3016+
Values<"none,untagged,tagged">, NormalizedValuesScope<"LangOptions::PointerFieldProtectionKind">,
3017+
NormalizedValues<["None", "Untagged", "Tagged"]>,
3018+
MarshallingInfoEnum<LangOpts<"PointerFieldProtection">, "None">;
3019+
30143020
def fcxx_abi_EQ : Joined<["-"], "fc++-abi=">,
30153021
Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
30163022
HelpText<"C++ ABI to use. This will override the target C++ ABI.">;

clang/lib/AST/ASTContext.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
#include "llvm/ADT/StringRef.h"
8181
#include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
8282
#include "llvm/Support/Capacity.h"
83+
#include "llvm/Support/CommandLine.h"
8384
#include "llvm/Support/Compiler.h"
8485
#include "llvm/Support/ErrorHandling.h"
8586
#include "llvm/Support/MD5.h"
@@ -15113,3 +15114,97 @@ bool ASTContext::useAbbreviatedThunkName(GlobalDecl VirtualMethodDecl,
1511315114
ThunksToBeAbbreviated[VirtualMethodDecl] = std::move(SimplifiedThunkNames);
1511415115
return Result;
1511515116
}
15117+
15118+
bool ASTContext::arePFPFieldsTriviallyRelocatable(const RecordDecl *RD) const {
15119+
if (getLangOpts().getPointerFieldProtection() ==
15120+
LangOptions::PointerFieldProtectionKind::Tagged)
15121+
return !isa<CXXRecordDecl>(RD) ||
15122+
cast<CXXRecordDecl>(RD)->hasTrivialDestructor();
15123+
return true;
15124+
}
15125+
15126+
bool ASTContext::isPFPStruct(const RecordDecl *rec) const {
15127+
if (getLangOpts().getPointerFieldProtection() !=
15128+
LangOptions::PointerFieldProtectionKind::None)
15129+
if (auto *cxxRec = dyn_cast<CXXRecordDecl>(rec))
15130+
return !cxxRec->isStandardLayout();
15131+
return false;
15132+
}
15133+
15134+
void ASTContext::findPFPFields(QualType Ty, CharUnits Offset,
15135+
std::vector<PFPField> &Fields,
15136+
bool IncludeVBases) const {
15137+
if (auto *AT = getAsConstantArrayType(Ty)) {
15138+
if (auto *ElemDecl = AT->getElementType()->getAsCXXRecordDecl()) {
15139+
const ASTRecordLayout &ElemRL = getASTRecordLayout(ElemDecl);
15140+
for (unsigned i = 0; i != AT->getSize(); ++i) {
15141+
findPFPFields(AT->getElementType(), Offset + i * ElemRL.getSize(),
15142+
Fields, true);
15143+
}
15144+
}
15145+
}
15146+
auto *Decl = Ty->getAsCXXRecordDecl();
15147+
if (!Decl)
15148+
return;
15149+
const ASTRecordLayout &RL = getASTRecordLayout(Decl);
15150+
for (FieldDecl *field : Decl->fields()) {
15151+
CharUnits fieldOffset =
15152+
Offset + toCharUnitsFromBits(RL.getFieldOffset(field->getFieldIndex()));
15153+
if (isPFPField(field))
15154+
Fields.push_back({Offset, fieldOffset, field});
15155+
findPFPFields(field->getType(), fieldOffset, Fields, true);
15156+
}
15157+
for (auto &Base : Decl->bases()) {
15158+
if (Base.isVirtual())
15159+
continue;
15160+
CharUnits BaseOffset =
15161+
Offset + RL.getBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
15162+
findPFPFields(Base.getType(), BaseOffset, Fields, false);
15163+
}
15164+
if (IncludeVBases) {
15165+
for (auto &Base : Decl->vbases()) {
15166+
CharUnits BaseOffset =
15167+
Offset + RL.getVBaseClassOffset(Base.getType()->getAsCXXRecordDecl());
15168+
findPFPFields(Base.getType(), BaseOffset, Fields, false);
15169+
}
15170+
}
15171+
}
15172+
15173+
bool ASTContext::hasPFPFields(QualType ty) const {
15174+
std::vector<PFPField> pfpFields;
15175+
findPFPFields(ty, CharUnits::Zero(), pfpFields, true);
15176+
return !pfpFields.empty();
15177+
}
15178+
15179+
bool ASTContext::isPFPField(const FieldDecl *field) const {
15180+
if (!isPFPStruct(field->getParent()))
15181+
return false;
15182+
return field->getType()->isPointerType() &&
15183+
!field->hasAttr<NoPointerFieldProtectionAttr>();
15184+
}
15185+
15186+
void ASTContext::recordMemberDataPointerEvaluation(const ValueDecl *VD) {
15187+
if (getLangOpts().getPointerFieldProtection() ==
15188+
LangOptions::PointerFieldProtectionKind::None)
15189+
return;
15190+
auto *FD = dyn_cast<FieldDecl>(VD);
15191+
if (!FD)
15192+
FD = cast<FieldDecl>(cast<IndirectFieldDecl>(VD)->chain().back());
15193+
if (!isPFPField(FD))
15194+
return;
15195+
PFPFieldsWithEvaluatedOffset.insert(FD);
15196+
}
15197+
15198+
void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
15199+
if (getLangOpts().getPointerFieldProtection() ==
15200+
LangOptions::PointerFieldProtectionKind::None ||
15201+
E->getNumComponents() == 0)
15202+
return;
15203+
OffsetOfNode Comp = E->getComponent(E->getNumComponents() - 1);
15204+
if (Comp.getKind() != OffsetOfNode::Field)
15205+
return;
15206+
FieldDecl *FD = Comp.getField();
15207+
if (!isPFPField(FD))
15208+
return;
15209+
PFPFieldsWithEvaluatedOffset.insert(FD);
15210+
}

clang/lib/AST/ExprConstant.cpp

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

1498114981
bool IntExprEvaluator::VisitOffsetOfExpr(const OffsetOfExpr *OOE) {
14982+
Info.Ctx.recordOffsetOfEvaluation(OOE);
1498214983
CharUnits Result;
1498314984
unsigned n = OOE->getNumComponents();
1498414985
if (n == 0)

clang/lib/AST/TypePrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2098,6 +2098,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
20982098
case attr::ExtVectorType:
20992099
OS << "ext_vector_type";
21002100
break;
2101+
case attr::NoPointerFieldProtection:
2102+
OS << "no_field_protection";
2103+
break;
21012104
}
21022105
OS << "))";
21032106
}

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4464,6 +4464,19 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
44644464
EmitArgCheck(TCK_Store, Dest, E->getArg(0), 0);
44654465
EmitArgCheck(TCK_Load, Src, E->getArg(1), 1);
44664466
Builder.CreateMemMove(Dest, Src, SizeVal, false);
4467+
if (BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_trivially_relocate) {
4468+
std::vector<PFPField> PFPFields;
4469+
getContext().findPFPFields(E->getArg(0)->getType()->getPointeeType(),
4470+
CharUnits::Zero(), PFPFields, true);
4471+
for (auto &Field : PFPFields) {
4472+
if (getContext().arePFPFieldsTriviallyRelocatable(
4473+
Field.field->getParent()))
4474+
continue;
4475+
auto DestFieldPtr = EmitAddressOfPFPField(Dest, Field);
4476+
auto SrcFieldPtr = EmitAddressOfPFPField(Src, Field);
4477+
Builder.CreateStore(Builder.CreateLoad(SrcFieldPtr), DestFieldPtr);
4478+
}
4479+
}
44674480
return RValue::get(Dest, *this);
44684481
}
44694482
case Builtin::BImemset:

0 commit comments

Comments
 (0)