Skip to content

Commit d90d4a9

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 1ff2953 commit d90d4a9

26 files changed

+735
-39
lines changed

clang/include/clang/AST/Type.h

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,7 +1991,7 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
19911991
/// Extra information which affects how the function is called, like
19921992
/// regparm and the calling convention.
19931993
LLVM_PREFERRED_TYPE(CallingConv)
1994-
unsigned ExtInfo : 14;
1994+
unsigned ExtInfo : 15;
19951995

19961996
/// The number of parameters this function has, not counting '...'.
19971997
/// According to [implimits] 8 bits should be enough here but this is
@@ -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 isCFIUncheckedCalleeFunctionType() const;
2570+
bool isPointerToCFIUncheckedCalleeFunctionType() const;
25692571
bool isBlockPointerType() const;
25702572
bool isVoidPointerType() const;
25712573
bool isReferenceType() const;
@@ -4496,8 +4498,8 @@ class FunctionType : public Type {
44964498
// adjust the Bits field below, and if you add bits, you'll need to adjust
44974499
// Type::FunctionTypeBitfields::ExtInfo as well.
44984500

4499-
// | CC |noreturn|produces|nocallersavedregs|regparm|nocfcheck|cmsenscall|
4500-
// |0 .. 5| 6 | 7 | 8 |9 .. 11| 12 | 13 |
4501+
// | CC |noreturn|produces|nocallersavedregs|regparm|nocfcheck|cmsenscall|cfiuncheckedcallee|
4502+
// |0 .. 5| 6 | 7 | 8 |9 .. 11| 12 | 13 | 14 |
45014503
//
45024504
// regparm is either 0 (no regparm attribute) or the regparm value+1.
45034505
enum { CallConvMask = 0x3F };
@@ -4507,6 +4509,7 @@ class FunctionType : public Type {
45074509
enum { RegParmMask = 0xe00, RegParmOffset = 9 };
45084510
enum { NoCfCheckMask = 0x1000 };
45094511
enum { CmseNSCallMask = 0x2000 };
4512+
enum { CFIUncheckedCalleeMask = 0x4000 };
45104513
uint16_t Bits = CC_C;
45114514

45124515
ExtInfo(unsigned Bits) : Bits(static_cast<uint16_t>(Bits)) {}
@@ -4516,14 +4519,15 @@ class FunctionType : public Type {
45164519
// have all the elements (when reading an AST file for example).
45174520
ExtInfo(bool noReturn, bool hasRegParm, unsigned regParm, CallingConv cc,
45184521
bool producesResult, bool noCallerSavedRegs, bool NoCfCheck,
4519-
bool cmseNSCall) {
4522+
bool cmseNSCall, bool cfiUncheckedCallee) {
45204523
assert((!hasRegParm || regParm < 7) && "Invalid regparm value");
45214524
Bits = ((unsigned)cc) | (noReturn ? NoReturnMask : 0) |
45224525
(producesResult ? ProducesResultMask : 0) |
45234526
(noCallerSavedRegs ? NoCallerSavedRegsMask : 0) |
45244527
(hasRegParm ? ((regParm + 1) << RegParmOffset) : 0) |
45254528
(NoCfCheck ? NoCfCheckMask : 0) |
4526-
(cmseNSCall ? CmseNSCallMask : 0);
4529+
(cmseNSCall ? CmseNSCallMask : 0) |
4530+
(cfiUncheckedCallee ? CFIUncheckedCalleeMask : 0);
45274531
}
45284532

45294533
// Constructor with all defaults. Use when for example creating a
@@ -4540,6 +4544,7 @@ class FunctionType : public Type {
45404544
bool getNoCallerSavedRegs() const { return Bits & NoCallerSavedRegsMask; }
45414545
bool getNoCfCheck() const { return Bits & NoCfCheckMask; }
45424546
bool getHasRegParm() const { return ((Bits & RegParmMask) >> RegParmOffset) != 0; }
4547+
bool getCFIUncheckedCallee() const { return Bits & CFIUncheckedCalleeMask; }
45434548

45444549
unsigned getRegParm() const {
45454550
unsigned RegParm = (Bits & RegParmMask) >> RegParmOffset;
@@ -4560,6 +4565,13 @@ class FunctionType : public Type {
45604565
// Note that we don't have setters. That is by design, use
45614566
// the following with methods instead of mutating these objects.
45624567

4568+
ExtInfo withCFIUncheckedCallee(bool cfiUncheckedCallee) const {
4569+
if (cfiUncheckedCallee)
4570+
return ExtInfo(Bits | CFIUncheckedCalleeMask);
4571+
else
4572+
return ExtInfo(Bits & ~CFIUncheckedCalleeMask);
4573+
}
4574+
45634575
ExtInfo withNoReturn(bool noReturn) const {
45644576
if (noReturn)
45654577
return ExtInfo(Bits | NoReturnMask);
@@ -4712,6 +4724,10 @@ class FunctionType : public Type {
47124724
/// type.
47134725
bool getNoReturnAttr() const { return getExtInfo().getNoReturn(); }
47144726

4727+
bool getCFIUncheckedCalleeAttr() const {
4728+
return getExtInfo().getCFIUncheckedCallee();
4729+
}
4730+
47154731
bool getCmseNSCallAttr() const { return getExtInfo().getCmseNSCall(); }
47164732
CallingConv getCallConv() const { return getExtInfo().getCC(); }
47174733
ExtInfo getExtInfo() const { return ExtInfo(FunctionTypeBits.ExtInfo); }
@@ -8253,6 +8269,19 @@ inline bool Type::isObjectPointerType() const {
82538269
return false;
82548270
}
82558271

8272+
inline bool Type::isCFIUncheckedCalleeFunctionType() const {
8273+
if (const auto *Fn = getAs<FunctionType>())
8274+
return Fn->getCFIUncheckedCalleeAttr();
8275+
return false;
8276+
}
8277+
8278+
inline bool Type::isPointerToCFIUncheckedCalleeFunctionType() const {
8279+
QualType Pointee = getPointeeType();
8280+
if (Pointee.isNull())
8281+
return false;
8282+
return Pointee->isCFIUncheckedCalleeFunctionType();
8283+
}
8284+
82568285
inline bool Type::isFunctionPointerType() const {
82578286
if (const auto *T = getAs<PointerType>())
82588287
return T->getPointeeType()->isFunctionType();

clang/include/clang/AST/TypeProperties.td

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,14 +298,17 @@ let Class = FunctionType in {
298298
def : Property<"cmseNSCall", Bool> {
299299
let Read = [{ node->getExtInfo().getCmseNSCall() }];
300300
}
301+
def : Property<"cfiUncheckedCallee", Bool> {
302+
let Read = [{ node->getExtInfo().getCFIUncheckedCallee() }];
303+
}
301304
}
302305

303306
let Class = FunctionNoProtoType in {
304307
def : Creator<[{
305308
auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm,
306309
callingConvention, producesResult,
307310
noCallerSavedRegs, noCfCheck,
308-
cmseNSCall);
311+
cmseNSCall, cfiUncheckedCallee);
309312
return ctx.getFunctionNoProtoType(returnType, extInfo);
310313
}]>;
311314
}
@@ -348,7 +351,7 @@ let Class = FunctionProtoType in {
348351
auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm,
349352
callingConvention, producesResult,
350353
noCallerSavedRegs, noCfCheck,
351-
cmseNSCall);
354+
cmseNSCall, cfiUncheckedCallee);
352355
FunctionProtoType::ExtProtoInfo epi;
353356
epi.ExtInfo = extInfo;
354357
epi.Variadic = variadic;

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/CodeGen/CGFunctionInfo.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,10 @@ class CGFunctionInfo final
602602
LLVM_PREFERRED_TYPE(bool)
603603
unsigned CmseNSCall : 1;
604604

605+
/// Whether this function is a CFI unchecked callee
606+
LLVM_PREFERRED_TYPE(bool)
607+
unsigned CFIUncheckedCallee : 1;
608+
605609
/// Whether this function is noreturn.
606610
LLVM_PREFERRED_TYPE(bool)
607611
unsigned NoReturn : 1;
@@ -704,6 +708,8 @@ class CGFunctionInfo final
704708

705709
bool isNoReturn() const { return NoReturn; }
706710

711+
bool isCFIUncheckedCallee() const { return CFIUncheckedCallee; }
712+
707713
/// In ARC, whether this function retains its return value. This
708714
/// is not always reliable for call sites.
709715
bool isReturnsRetained() const { return ReturnsRetained; }
@@ -740,7 +746,7 @@ class CGFunctionInfo final
740746
return FunctionType::ExtInfo(isNoReturn(), getHasRegParm(), getRegParm(),
741747
getASTCallingConvention(), isReturnsRetained(),
742748
isNoCallerSavedRegs(), isNoCfCheck(),
743-
isCmseNSCall());
749+
isCmseNSCall(), isCFIUncheckedCallee());
744750
}
745751

746752
CanQualType getReturnType() const { return getArgsBuffer()[0].type; }
@@ -794,6 +800,7 @@ class CGFunctionInfo final
794800
ID.AddInteger(RegParm);
795801
ID.AddBoolean(NoCfCheck);
796802
ID.AddBoolean(CmseNSCall);
803+
ID.AddBoolean(CFIUncheckedCallee);
797804
ID.AddInteger(Required.getOpaqueData());
798805
ID.AddBoolean(HasExtParameterInfos);
799806
if (HasExtParameterInfos) {
@@ -821,6 +828,7 @@ class CGFunctionInfo final
821828
ID.AddInteger(info.getRegParm());
822829
ID.AddBoolean(info.getNoCfCheck());
823830
ID.AddBoolean(info.getCmseNSCall());
831+
ID.AddBoolean(info.getCFIUncheckedCallee());
824832
ID.AddInteger(required.getOpaqueData());
825833
ID.AddBoolean(!paramInfos.empty());
826834
if (!paramInfos.empty()) {

clang/include/clang/Sema/Sema.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10222,7 +10222,10 @@ class Sema final : public SemaBase {
1022210222
/// conversion that strips "noexcept" or "noreturn" off the nested function
1022310223
/// type.
1022410224
bool IsFunctionConversion(QualType FromType, QualType ToType,
10225-
QualType &ResultTy);
10225+
bool *DiscardingCFIUncheckedCallee = nullptr,
10226+
bool *AddingCFIUncheckedCallee = nullptr) const;
10227+
bool TryFunctionConversion(QualType FromType, QualType ToType,
10228+
QualType &ResultTy);
1022610229
bool DiagnoseMultipleUserDefinedConversion(Expr *From, QualType ToType);
1022710230
void DiagnoseUseOfDeletedFunction(SourceLocation Loc, SourceRange Range,
1022810231
DeclarationName Name,

clang/lib/AST/TypePrinter.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,8 @@ void TypePrinter::printFunctionAfter(const FunctionType::ExtInfo &Info,
11581158

11591159
if (Info.getNoReturn())
11601160
OS << " __attribute__((noreturn))";
1161+
if (Info.getCFIUncheckedCallee())
1162+
OS << " __attribute__((cfi_unchecked_callee))";
11611163
if (Info.getCmseNSCall())
11621164
OS << " __attribute__((cmse_nonsecure_call))";
11631165
if (Info.getProducesResult())
@@ -2089,6 +2091,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
20892091
case attr::NoDeref:
20902092
OS << "noderef";
20912093
break;
2094+
case attr::CFIUncheckedCallee:
2095+
OS << "cfi_unchecked_callee";
2096+
break;
20922097
case attr::AcquireHandle:
20932098
OS << "acquire_handle";
20942099
break;

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3003,6 +3003,10 @@ static LValue EmitFunctionDeclLValue(CodeGenFunction &CGF, const Expr *E,
30033003
GlobalDecl GD) {
30043004
const FunctionDecl *FD = cast<FunctionDecl>(GD.getDecl());
30053005
llvm::Constant *V = CGF.CGM.getFunctionPointer(GD);
3006+
if (E->getType()->isCFIUncheckedCalleeFunctionType()) {
3007+
if (auto *GV = dyn_cast<llvm::GlobalValue>(V))
3008+
V = llvm::NoCFIValue::get(GV);
3009+
}
30063010
CharUnits Alignment = CGF.getContext().getDeclAlign(FD);
30073011
return CGF.MakeAddrLValue(V, E->getType(), Alignment,
30083012
AlignmentSource::Decl);
@@ -6238,10 +6242,12 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
62386242
FD && FD->hasAttr<OpenCLKernelAttr>())
62396243
CGM.getTargetCodeGenInfo().setOCLKernelStubCallingConvention(FnType);
62406244

6245+
bool CFIUnchecked = CalleeType->isPointerToCFIUncheckedCalleeFunctionType();
6246+
62416247
// If we are checking indirect calls and this call is indirect, check that the
62426248
// function pointer is a member of the bit set for the function type.
62436249
if (SanOpts.has(SanitizerKind::CFIICall) &&
6244-
(!TargetDecl || !isa<FunctionDecl>(TargetDecl))) {
6250+
(!TargetDecl || !isa<FunctionDecl>(TargetDecl)) && !CFIUnchecked) {
62456251
SanitizerScope SanScope(this);
62466252
EmitSanitizerStatReport(llvm::SanStat_CFI_ICall);
62476253

clang/lib/CodeGen/CGExprConstant.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2236,8 +2236,12 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) {
22362236
return ConstantLValue(C);
22372237
};
22382238

2239-
if (const auto *FD = dyn_cast<FunctionDecl>(D))
2240-
return PtrAuthSign(CGM.getRawFunctionPointer(FD));
2239+
if (const auto *FD = dyn_cast<FunctionDecl>(D)) {
2240+
llvm::Constant *C = CGM.getRawFunctionPointer(FD);
2241+
if (FD->getType()->isCFIUncheckedCalleeFunctionType())
2242+
C = llvm::NoCFIValue::get(cast<llvm::GlobalValue>(C));
2243+
return PtrAuthSign(C);
2244+
}
22412245

22422246
if (const auto *VD = dyn_cast<VarDecl>(D)) {
22432247
// We can never refer to a variable with local storage.

clang/lib/CodeGen/CGPointerAuth.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,11 @@ llvm::Constant *CodeGenModule::getMemberFunctionPointer(llvm::Constant *Pointer,
517517
Pointer, PointerAuth.getKey(), nullptr,
518518
cast_or_null<llvm::ConstantInt>(PointerAuth.getDiscriminator()));
519519

520+
if (const auto *MFT = dyn_cast<MemberPointerType>(FT.getTypePtr())) {
521+
if (MFT->isPointerToCFIUncheckedCalleeFunctionType())
522+
Pointer = llvm::NoCFIValue::get(cast<llvm::GlobalValue>(Pointer));
523+
}
524+
520525
return Pointer;
521526
}
522527

clang/lib/CodeGen/ItaniumCXXABI.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,18 @@ CGCallee ItaniumCXXABI::EmitLoadOfMemberFunctionPointer(
693693
llvm::Constant *CheckTypeDesc;
694694
bool ShouldEmitCFICheck = CGF.SanOpts.has(SanitizerKind::CFIMFCall) &&
695695
CGM.HasHiddenLTOVisibility(RD);
696+
697+
if (ShouldEmitCFICheck) {
698+
if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
699+
if (BinOp->isPtrMemOp()) {
700+
if (BinOp->getRHS()
701+
->getType()
702+
->isPointerToCFIUncheckedCalleeFunctionType())
703+
ShouldEmitCFICheck = false;
704+
}
705+
}
706+
}
707+
696708
bool ShouldEmitVFEInfo = CGM.getCodeGenOpts().VirtualFunctionElimination &&
697709
CGM.HasHiddenLTOVisibility(RD);
698710
bool ShouldEmitWPDInfo =

0 commit comments

Comments
 (0)