Skip to content

Commit bcb9980

Browse files
committed
[clang] Function type attribute to prevent CFI instrumentation
This introduces the attribute discussed in https://discourse.llvm.org/t/rfc-function-type-attribute-to-prevent-cfi-instrumentation/85458. The proposed name has been changed from `no_cfi` to `cfi_unchecked_callee` to help differentiate from `no_sanitize("cfi")` more easily. The proposed attribute has the following semantics: 1. Indirect calls to a function type with this attribute will not be instrumented with CFI. That is, the indirect call will not be checked. Note that this only changes the behavior for indirect calls on pointers to function types having this attribute. It does not prevent all indirect function calls for a given type from being checked. 2. All direct references to a function whose type has this attribute will always reference the true function definition rather than an entry in the CFI jump table. 3. When a pointer to a function with this attribute is implicitly cast to a pointer to a function without this attribute, the compiler will give a warning saying this attribute is discarded. This warning can be silenced with an explicit C-style cast or C++ static_cast.
1 parent a4e9cfd commit bcb9980

40 files changed

+803
-7
lines changed

clang/include/clang/AST/ASTContext.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ class ASTContext : public RefCountedBase<ASTContext> {
262262

263263
mutable llvm::FoldingSet<CountAttributedType> CountAttributedTypes;
264264

265+
mutable llvm::FoldingSet<CFIUncheckedCalleeType> CFIUncheckedCalleeTypes;
266+
265267
mutable llvm::FoldingSet<QualifiedTemplateName> QualifiedTemplateNames;
266268
mutable llvm::FoldingSet<DependentTemplateName> DependentTemplateNames;
267269
mutable llvm::FoldingSet<SubstTemplateTemplateParmStorage>
@@ -1464,6 +1466,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
14641466
bool OrNull,
14651467
ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const;
14661468

1469+
/// Return a type wrapped with the `cfi_unchecked_callee` attribute.
1470+
QualType getCFIUncheckedCalleeType(QualType Wrapped) const;
1471+
14671472
/// Return the uniqued reference to a type adjusted from the original
14681473
/// type to a new type.
14691474
QualType getAdjustedType(QualType Orig, QualType New) const;

clang/include/clang/AST/ASTNodeTraverser.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,9 @@ class ASTNodeTraverser
445445
void VisitBTFTagAttributedType(const BTFTagAttributedType *T) {
446446
Visit(T->getWrappedType());
447447
}
448+
void VisitCFIUncheckedCalleeType(const CFIUncheckedCalleeType *T) {
449+
Visit(T->getWrappedType());
450+
}
448451
void VisitHLSLAttributedResourceType(const HLSLAttributedResourceType *T) {
449452
QualType Contained = T->getContainedType();
450453
if (!Contained.isNull())

clang/include/clang/AST/RecursiveASTVisitor.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,6 +1142,9 @@ DEF_TRAVERSE_TYPE(InjectedClassNameType, {})
11421142
DEF_TRAVERSE_TYPE(AttributedType,
11431143
{ TRY_TO(TraverseType(T->getModifiedType())); })
11441144

1145+
DEF_TRAVERSE_TYPE(CFIUncheckedCalleeType,
1146+
{ TRY_TO(TraverseType(T->getWrappedType())); })
1147+
11451148
DEF_TRAVERSE_TYPE(CountAttributedType, {
11461149
if (T->getCountExpr())
11471150
TRY_TO(TraverseStmt(T->getCountExpr()));
@@ -1448,6 +1451,9 @@ DEF_TRAVERSE_TYPELOC(MacroQualifiedType,
14481451
DEF_TRAVERSE_TYPELOC(AttributedType,
14491452
{ TRY_TO(TraverseTypeLoc(TL.getModifiedLoc())); })
14501453

1454+
DEF_TRAVERSE_TYPELOC(CFIUncheckedCalleeType,
1455+
{ TRY_TO(TraverseTypeLoc(TL.getWrappedLoc())); })
1456+
14511457
DEF_TRAVERSE_TYPELOC(CountAttributedType,
14521458
{ TRY_TO(TraverseTypeLoc(TL.getInnerLoc())); })
14531459

clang/include/clang/AST/Type.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2566,6 +2566,8 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
25662566
bool isSignableType() const;
25672567
bool isAnyPointerType() const; // Any C pointer or ObjC object pointer
25682568
bool isCountAttributedType() const;
2569+
bool isCFIUncheckedCalleeType() const;
2570+
bool isPointerToCFIUncheckedCalleeType() const;
25692571
bool isBlockPointerType() const;
25702572
bool isVoidPointerType() const;
25712573
bool isReferenceType() const;
@@ -3060,6 +3062,10 @@ template <> const TemplateSpecializationType *Type::getAs() const;
30603062
/// until it reaches an AttributedType or a non-sugared type.
30613063
template <> const AttributedType *Type::getAs() const;
30623064

3065+
/// This will check for an CFIUncheckedCalleeType by removing any existing sugar
3066+
/// until it reaches an CFIUncheckedCalleeType or a non-sugared type.
3067+
template <> const CFIUncheckedCalleeType *Type::getAs() const;
3068+
30633069
/// This will check for a BoundsAttributedType by removing any existing
30643070
/// sugar until it reaches an BoundsAttributedType or a non-sugared type.
30653071
template <> const BoundsAttributedType *Type::getAs() const;
@@ -3349,6 +3355,30 @@ class BoundsAttributedType : public Type, public llvm::FoldingSetNode {
33493355
}
33503356
};
33513357

3358+
class CFIUncheckedCalleeType : public Type, public llvm::FoldingSetNode {
3359+
public:
3360+
bool isSugared() const { return true; }
3361+
QualType desugar() const { return WrappedTy; }
3362+
3363+
QualType getWrappedType() const { return WrappedTy; }
3364+
3365+
static bool classof(const Type *T) {
3366+
return T->getTypeClass() == CFIUncheckedCallee;
3367+
}
3368+
3369+
static void Profile(llvm::FoldingSetNodeID &ID, QualType WrappedTy);
3370+
3371+
void Profile(llvm::FoldingSetNodeID &ID) { Profile(ID, WrappedTy); }
3372+
3373+
protected:
3374+
friend class ASTContext;
3375+
3376+
CFIUncheckedCalleeType(QualType Wrapped, QualType Canon);
3377+
3378+
private:
3379+
QualType WrappedTy;
3380+
};
3381+
33523382
/// Represents a sugar type with `__counted_by` or `__sized_by` annotations,
33533383
/// including their `_or_null` variants.
33543384
class CountAttributedType final
@@ -8244,6 +8274,20 @@ inline bool Type::isObjectPointerType() const {
82448274
return false;
82458275
}
82468276

8277+
inline bool Type::isCFIUncheckedCalleeType() const {
8278+
return getAs<CFIUncheckedCalleeType>();
8279+
}
8280+
8281+
inline bool Type::isPointerToCFIUncheckedCalleeType() const {
8282+
if (const auto *MFT = getAs<MemberPointerType>()) {
8283+
if (MFT->isMemberFunctionPointer())
8284+
return MFT->getPointeeType()->getAs<CFIUncheckedCalleeType>();
8285+
}
8286+
if (const auto *T = getAs<PointerType>())
8287+
return T->getPointeeType()->getAs<CFIUncheckedCalleeType>();
8288+
return false;
8289+
}
8290+
82478291
inline bool Type::isFunctionPointerType() const {
82488292
if (const auto *T = getAs<PointerType>())
82498293
return T->getPointeeType()->isFunctionType();
@@ -8799,6 +8843,8 @@ template <typename T> const T *Type::getAsAdjusted() const {
87998843
Ty = A->desugar().getTypePtr();
88008844
else if (const auto *M = dyn_cast<MacroQualifiedType>(Ty))
88018845
Ty = M->desugar().getTypePtr();
8846+
else if (const auto *M = dyn_cast<CFIUncheckedCalleeType>(Ty))
8847+
Ty = M->desugar().getTypePtr();
88028848
else
88038849
break;
88048850
}

clang/include/clang/AST/TypeLoc.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,31 @@ class HLSLAttributedResourceTypeLoc
973973
}
974974
};
975975

976+
struct CFIUncheckedCalleeLocInfo {
977+
SourceLocation AttrLoc;
978+
};
979+
980+
/// Type source information for the CFIUncheckedCallee Type
981+
class CFIUncheckedCalleeTypeLoc
982+
: public ConcreteTypeLoc<UnqualTypeLoc, CFIUncheckedCalleeTypeLoc,
983+
CFIUncheckedCalleeType,
984+
CFIUncheckedCalleeLocInfo> {
985+
public:
986+
TypeLoc getWrappedLoc() const { return getInnerTypeLoc(); }
987+
988+
void setAttrLoc(const SourceLocation &Loc) { getLocalData()->AttrLoc = Loc; }
989+
SourceLocation getAttrLoc() const { return getLocalData()->AttrLoc; }
990+
void initializeLocal(ASTContext &Context, SourceLocation Loc) {
991+
setAttrLoc(Loc);
992+
}
993+
QualType getWrappedType() const { return getTypePtr()->getWrappedType(); }
994+
QualType getInnerType() const { return getTypePtr()->getWrappedType(); }
995+
996+
SourceRange getLocalSourceRange() const {
997+
return getInnerTypeLoc().getLocalSourceRange();
998+
}
999+
};
1000+
9761001
struct ObjCObjectTypeLocInfo {
9771002
SourceLocation TypeArgsLAngleLoc;
9781003
SourceLocation TypeArgsRAngleLoc;
@@ -2736,6 +2761,8 @@ inline T TypeLoc::getAsAdjusted() const {
27362761
Cur = ATL.getOriginalLoc();
27372762
else if (auto MQL = Cur.getAs<MacroQualifiedTypeLoc>())
27382763
Cur = MQL.getInnerLoc();
2764+
else if (auto CFIUCL = Cur.getAs<CFIUncheckedCalleeTypeLoc>())
2765+
Cur = CFIUCL.getWrappedLoc();
27392766
else
27402767
break;
27412768
}

clang/include/clang/AST/TypeProperties.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,15 @@ let Class = ParenType in {
633633
}]>;
634634
}
635635

636+
let Class = CFIUncheckedCalleeType in {
637+
def : Property<"WrappedTy", QualType> {
638+
let Read = [{ node->getWrappedType() }];
639+
}
640+
def : Creator<[{
641+
return ctx.getCFIUncheckedCalleeType(WrappedTy);
642+
}]>;
643+
}
644+
636645
let Class = MacroQualifiedType in {
637646
def : Property<"underlyingType", QualType> {
638647
let Read = [{ node->getUnderlyingType() }];

clang/include/clang/Basic/Attr.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3082,6 +3082,11 @@ def NoDeref : TypeAttr {
30823082
let Documentation = [NoDerefDocs];
30833083
}
30843084

3085+
def CFIUncheckedCallee : TypeAttr {
3086+
let Spellings = [Clang<"cfi_unchecked_callee">];
3087+
let Documentation = [CFIUncheckedCalleeDocs];
3088+
}
3089+
30853090
def ReqdWorkGroupSize : InheritableAttr {
30863091
// Does not have a [[]] spelling because it is an OpenCL-related attribute.
30873092
let Spellings = [GNU<"reqd_work_group_size">];

clang/include/clang/Basic/AttrDocs.td

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6869,6 +6869,54 @@ for references or Objective-C object pointers.
68696869
}];
68706870
}
68716871

6872+
def CFIUncheckedCalleeDocs : Documentation {
6873+
let Category = DocCatType;
6874+
let Content = [{
6875+
``cfi_unchecked_callee`` is a function type attribute which prevents the compiler from instrumenting
6876+
`Control Flow Integrity <https://clang.llvm.org/docs/ControlFlowIntegrity.html>`_ checks on indirect
6877+
function calls. Specifically, the attribute has the following semantics:
6878+
6879+
1. Indirect calls to a function type with this attribute will not be instrumented with CFI. That is,
6880+
the indirect call will not be checked. Note that this only changes the behavior for indirect calls
6881+
on pointers to function types having this attribute. It does not prevent all indirect function calls
6882+
for a given type from being checked.
6883+
2. All direct references to a function whose type has this attribute will always reference the
6884+
function definition rather than an entry in the CFI jump table.
6885+
3. When a pointer to a function with this attribute is implicitly cast to a pointer to a function
6886+
without this attribute, the compiler will give a warning saying this attribute is discarded. This
6887+
warning can be silenced with an explicit cast. Note an explicit cast just disables the warning, so
6888+
direct references to a function with a ``cfi_unchecked_callee`` attribute will still reference the
6889+
function definition rather than the CFI jump table.
6890+
6891+
.. code-block:: c
6892+
6893+
#define CFI_UNCHECKED_CALLEE __attribute__((cfi_unchecked_callee))
6894+
6895+
void no_cfi() CFI_UNCHECKED_CALLEE {}
6896+
6897+
void (*with_cfi)() = no_cfi; // warning: implicit conversion discards `cfi_unchecked_callee` attribute.
6898+
// `with_cfi` also points to the actual definition of `no_cfi` rather than
6899+
// its jump table entry.
6900+
6901+
void invoke(void (CFI_UNCHECKED_CALLEE *func)()) {
6902+
func(); // CFI will not instrument this indirect call.
6903+
6904+
void (*func2)() = func; // warning: implicit conversion discards `cfi_unchecked_callee` attribute.
6905+
6906+
func2(); // CFI will instrument this indirect call. Users should be careful however because if this
6907+
// references a function with type `cfi_unchecked_callee`, then the CFI check may incorrectly
6908+
// fail because the reference will be to the function definition rather than the CFI jump
6909+
// table entry.
6910+
}
6911+
6912+
This attribute can only be applied on functions or member functions. This attribute can be a good
6913+
alternative to ``no_sanitize("cfi")`` if you only want to disable innstrumentation for specific indirect
6914+
calls rather than applying ``no_sanitize("cfi")`` on the whole function containing indirect call. Note
6915+
that ``cfi_unchecked_attribute`` is a type attribute doesn't disable CFI instrumentation on a function
6916+
body.
6917+
}];
6918+
}
6919+
68726920
def ReinitializesDocs : Documentation {
68736921
let Category = DocCatFunction;
68746922
let Content = [{

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1576,6 +1576,8 @@ def FunctionMultiVersioning
15761576

15771577
def NoDeref : DiagGroup<"noderef">;
15781578

1579+
def CFIUncheckedCallee : DiagGroup<"cfi-unchecked-callee">;
1580+
15791581
// -fbounds-safety and bounds annotation related warnings
15801582
def BoundsSafetyCountedByEltTyUnknownSize :
15811583
DiagGroup<"bounds-safety-counted-by-elt-type-unknown-size">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12628,6 +12628,15 @@ def warn_noderef_on_non_pointer_or_array : Warning<
1262812628
def warn_noderef_to_dereferenceable_pointer : Warning<
1262912629
"casting to dereferenceable pointer removes 'noderef' attribute">, InGroup<NoDeref>;
1263012630

12631+
def warn_cfi_unchecked_callee_on_non_function
12632+
: Warning<"use of `cfi_unchecked_callee` on %0; can only be used on "
12633+
"function types">,
12634+
InGroup<CFIUncheckedCallee>;
12635+
def warn_cast_discards_cfi_unchecked_callee
12636+
: Warning<"implicit conversion from %0 to %1 discards "
12637+
"`cfi_unchecked_callee` attribute">,
12638+
InGroup<CFIUncheckedCallee>;
12639+
1263112640
def err_builtin_launder_invalid_arg : Error<
1263212641
"%select{non-pointer|function pointer|void pointer}0 argument to "
1263312642
"'__builtin_launder' is not allowed">;

clang/include/clang/Basic/TypeNodes.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ def ObjCObjectType : TypeNode<Type>;
111111
def ObjCInterfaceType : TypeNode<ObjCObjectType>, LeafType;
112112
def ObjCObjectPointerType : TypeNode<Type>;
113113
def BoundsAttributedType : TypeNode<Type, 1>;
114+
def CFIUncheckedCalleeType : TypeNode<Type>, NeverCanonical;
114115
def CountAttributedType : TypeNode<BoundsAttributedType>, NeverCanonical;
115116
def PipeType : TypeNode<Type>;
116117
def AtomicType : TypeNode<Type>;

clang/include/clang/Serialization/TypeBitCodes.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@ TYPE_BIT_CODE(PackIndexing, PACK_INDEXING, 56)
6868
TYPE_BIT_CODE(CountAttributed, COUNT_ATTRIBUTED, 57)
6969
TYPE_BIT_CODE(ArrayParameter, ARRAY_PARAMETER, 58)
7070
TYPE_BIT_CODE(HLSLAttributedResource, HLSLRESOURCE_ATTRIBUTED, 59)
71+
TYPE_BIT_CODE(CFIUncheckedCallee, CFI_UNCHECKED_CALLEE, 60)
7172

7273
#undef TYPE_BIT_CODE

clang/lib/AST/ASTContext.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2464,6 +2464,10 @@ TypeInfo ASTContext::getTypeInfoImpl(const Type *T) const {
24642464
return getTypeInfo(
24652465
cast<AttributedType>(T)->getEquivalentType().getTypePtr());
24662466

2467+
case Type::CFIUncheckedCallee:
2468+
return getTypeInfo(
2469+
cast<CFIUncheckedCalleeType>(T)->getWrappedType().getTypePtr());
2470+
24672471
case Type::CountAttributed:
24682472
return getTypeInfo(cast<CountAttributedType>(T)->desugar().getTypePtr());
24692473

@@ -3589,6 +3593,26 @@ QualType ASTContext::removePtrSizeAddrSpace(QualType T) const {
35893593
return T;
35903594
}
35913595

3596+
QualType ASTContext::getCFIUncheckedCalleeType(QualType Wrapped) const {
3597+
llvm::FoldingSetNodeID ID;
3598+
CFIUncheckedCalleeType::Profile(ID, Wrapped);
3599+
3600+
void *InsertPos = nullptr;
3601+
CFIUncheckedCalleeType *Ty =
3602+
CFIUncheckedCalleeTypes.FindNodeOrInsertPos(ID, InsertPos);
3603+
if (Ty)
3604+
return QualType(Ty, 0);
3605+
3606+
QualType CanonTy = getCanonicalType(Wrapped);
3607+
Ty = new (*this, alignof(CFIUncheckedCalleeType))
3608+
CFIUncheckedCalleeType(Wrapped, CanonTy);
3609+
3610+
Types.push_back(Ty);
3611+
CFIUncheckedCalleeTypes.InsertNode(Ty, InsertPos);
3612+
3613+
return QualType(Ty, 0);
3614+
}
3615+
35923616
QualType ASTContext::getCountAttributedType(
35933617
QualType WrappedTy, Expr *CountExpr, bool CountInBytes, bool OrNull,
35943618
ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const {
@@ -3655,6 +3679,12 @@ ASTContext::adjustType(QualType Orig,
36553679
MQT->getMacroIdentifier());
36563680
}
36573681

3682+
case Type::CFIUncheckedCallee: {
3683+
const auto *CFIUCT = cast<CFIUncheckedCalleeType>(Orig);
3684+
return getCFIUncheckedCalleeType(
3685+
adjustType(CFIUCT->getWrappedType(), Adjust));
3686+
}
3687+
36583688
default:
36593689
return Adjust(Orig);
36603690
}
@@ -14223,6 +14253,15 @@ static QualType getCommonSugarTypeNode(ASTContext &Ctx, const Type *X,
1422314253
return Ctx.getDecayedType(Ctx.getCommonSugaredType(OX, OY),
1422414254
Ctx.getQualifiedType(Underlying));
1422514255
}
14256+
case Type::CFIUncheckedCallee: {
14257+
const auto *CX = cast<CFIUncheckedCalleeType>(X),
14258+
*CY = cast<CFIUncheckedCalleeType>(Y);
14259+
QualType WX = CX->getWrappedType(), WY = CY->getWrappedType();
14260+
if (!Ctx.hasSameType(WX, WY))
14261+
return QualType();
14262+
// FIXME: It's inefficient to have to unify the wrapped types.
14263+
return Ctx.getCFIUncheckedCalleeType(Ctx.getCommonSugaredType(WX, WY));
14264+
}
1422614265
case Type::Attributed: {
1422714266
const auto *AX = cast<AttributedType>(X), *AY = cast<AttributedType>(Y);
1422814267
AttributedType::Kind Kind = AX->getAttrKind();

clang/lib/AST/ASTDiagnostic.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ QualType clang::desugarForDiagnostic(ASTContext &Context, QualType QT,
5454
QT = MDT->desugar();
5555
continue;
5656
}
57+
// ... or a cfi_unchecked_callee type ...
58+
if (const CFIUncheckedCalleeType *CFIUCT =
59+
dyn_cast<CFIUncheckedCalleeType>(Ty)) {
60+
QT = CFIUCT->desugar();
61+
continue;
62+
}
5763
// ...or a substituted template type parameter ...
5864
if (const SubstTemplateTypeParmType *ST =
5965
dyn_cast<SubstTemplateTypeParmType>(Ty)) {

clang/lib/AST/ASTImporter.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1784,6 +1784,15 @@ ASTNodeImporter::VisitMacroQualifiedType(const MacroQualifiedType *T) {
17841784
ToIdentifier);
17851785
}
17861786

1787+
ExpectedType
1788+
ASTNodeImporter::VisitCFIUncheckedCalleeType(const CFIUncheckedCalleeType *T) {
1789+
ExpectedType ToWrappedTypeOrErr = import(T->getWrappedType());
1790+
if (!ToWrappedTypeOrErr)
1791+
return ToWrappedTypeOrErr.takeError();
1792+
1793+
return Importer.getToContext().getCFIUncheckedCalleeType(*ToWrappedTypeOrErr);
1794+
}
1795+
17871796
ExpectedType clang::ASTNodeImporter::VisitAdjustedType(const AdjustedType *T) {
17881797
Error Err = Error::success();
17891798
QualType ToOriginalType = importChecked(Err, T->getOriginalType());

0 commit comments

Comments
 (0)