Skip to content

Commit b194cf1

Browse files
authored
[clang] Function type attribute to prevent CFI instrumentation (#135836)
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 f32f048 commit b194cf1

23 files changed

+791
-20
lines changed

clang/include/clang/AST/Type.h

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1985,6 +1985,10 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
19851985
LLVM_PREFERRED_TYPE(bool)
19861986
unsigned HasTrailingReturn : 1;
19871987

1988+
/// Whether this function has is a cfi unchecked callee.
1989+
LLVM_PREFERRED_TYPE(bool)
1990+
unsigned CFIUncheckedCallee : 1;
1991+
19881992
/// Extra information which affects how the function is called, like
19891993
/// regparm and the calling convention.
19901994
LLVM_PREFERRED_TYPE(CallingConv)
@@ -2566,6 +2570,8 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
25662570
bool isSignableIntegerType(const ASTContext &Ctx) const;
25672571
bool isAnyPointerType() const; // Any C pointer or ObjC object pointer
25682572
bool isCountAttributedType() const;
2573+
bool isCFIUncheckedCalleeFunctionType() const;
2574+
bool hasPointeeToToCFIUncheckedCalleeFunctionType() const;
25692575
bool isBlockPointerType() const;
25702576
bool isVoidPointerType() const;
25712577
bool isReferenceType() const;
@@ -4713,6 +4719,10 @@ class FunctionType : public Type {
47134719
/// type.
47144720
bool getNoReturnAttr() const { return getExtInfo().getNoReturn(); }
47154721

4722+
/// Determine whether this is a function prototype that includes the
4723+
/// cfi_unchecked_callee attribute.
4724+
bool getCFIUncheckedCalleeAttr() const;
4725+
47164726
bool getCmseNSCallAttr() const { return getExtInfo().getCmseNSCall(); }
47174727
CallingConv getCallConv() const { return getExtInfo().getCC(); }
47184728
ExtInfo getExtInfo() const { return ExtInfo(FunctionTypeBits.ExtInfo); }
@@ -5249,8 +5259,12 @@ class FunctionProtoType final
52495259
/// the various bits of extra information about a function prototype.
52505260
struct ExtProtoInfo {
52515261
FunctionType::ExtInfo ExtInfo;
5262+
LLVM_PREFERRED_TYPE(bool)
52525263
unsigned Variadic : 1;
5264+
LLVM_PREFERRED_TYPE(bool)
52535265
unsigned HasTrailingReturn : 1;
5266+
LLVM_PREFERRED_TYPE(bool)
5267+
unsigned CFIUncheckedCallee : 1;
52545268
unsigned AArch64SMEAttributes : 9;
52555269
Qualifiers TypeQuals;
52565270
RefQualifierKind RefQualifier = RQ_None;
@@ -5260,19 +5274,25 @@ class FunctionProtoType final
52605274
FunctionEffectsRef FunctionEffects;
52615275

52625276
ExtProtoInfo()
5263-
: Variadic(false), HasTrailingReturn(false),
5277+
: Variadic(false), HasTrailingReturn(false), CFIUncheckedCallee(false),
52645278
AArch64SMEAttributes(SME_NormalFunction) {}
52655279

52665280
ExtProtoInfo(CallingConv CC)
52675281
: ExtInfo(CC), Variadic(false), HasTrailingReturn(false),
5268-
AArch64SMEAttributes(SME_NormalFunction) {}
5282+
CFIUncheckedCallee(false), AArch64SMEAttributes(SME_NormalFunction) {}
52695283

52705284
ExtProtoInfo withExceptionSpec(const ExceptionSpecInfo &ESI) {
52715285
ExtProtoInfo Result(*this);
52725286
Result.ExceptionSpec = ESI;
52735287
return Result;
52745288
}
52755289

5290+
ExtProtoInfo withCFIUncheckedCallee(bool CFIUncheckedCallee) {
5291+
ExtProtoInfo Result(*this);
5292+
Result.CFIUncheckedCallee = CFIUncheckedCallee;
5293+
return Result;
5294+
}
5295+
52765296
bool requiresFunctionProtoTypeExtraBitfields() const {
52775297
return ExceptionSpec.Type == EST_Dynamic ||
52785298
requiresFunctionProtoTypeArmAttributes() ||
@@ -5432,6 +5452,7 @@ class FunctionProtoType final
54325452
EPI.Variadic = isVariadic();
54335453
EPI.EllipsisLoc = getEllipsisLoc();
54345454
EPI.HasTrailingReturn = hasTrailingReturn();
5455+
EPI.CFIUncheckedCallee = hasCFIUncheckedCallee();
54355456
EPI.ExceptionSpec = getExceptionSpecInfo();
54365457
EPI.TypeQuals = getMethodQuals();
54375458
EPI.RefQualifier = getRefQualifier();
@@ -5557,6 +5578,10 @@ class FunctionProtoType final
55575578
/// Whether this function prototype has a trailing return type.
55585579
bool hasTrailingReturn() const { return FunctionTypeBits.HasTrailingReturn; }
55595580

5581+
bool hasCFIUncheckedCallee() const {
5582+
return FunctionTypeBits.CFIUncheckedCallee;
5583+
}
5584+
55605585
Qualifiers getMethodQuals() const {
55615586
if (hasExtQualifiers())
55625587
return *getTrailingObjects<Qualifiers>();
@@ -8391,6 +8416,27 @@ inline bool Type::isObjectPointerType() const {
83918416
return false;
83928417
}
83938418

8419+
inline bool Type::isCFIUncheckedCalleeFunctionType() const {
8420+
if (const auto *Fn = getAs<FunctionProtoType>())
8421+
return Fn->hasCFIUncheckedCallee();
8422+
return false;
8423+
}
8424+
8425+
inline bool Type::hasPointeeToToCFIUncheckedCalleeFunctionType() const {
8426+
QualType Pointee;
8427+
if (const auto *PT = getAs<PointerType>())
8428+
Pointee = PT->getPointeeType();
8429+
else if (const auto *RT = getAs<ReferenceType>())
8430+
Pointee = RT->getPointeeType();
8431+
else if (const auto *MPT = getAs<MemberPointerType>())
8432+
Pointee = MPT->getPointeeType();
8433+
else if (const auto *DT = getAs<DecayedType>())
8434+
Pointee = DT->getPointeeType();
8435+
else
8436+
return false;
8437+
return Pointee->isCFIUncheckedCalleeFunctionType();
8438+
}
8439+
83948440
inline bool Type::isFunctionPointerType() const {
83958441
if (const auto *T = getAs<PointerType>())
83968442
return T->getPointeeType()->isFunctionType();

clang/include/clang/AST/TypeProperties.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,9 @@ let Class = FunctionProtoType in {
317317
def : Property<"trailingReturn", Bool> {
318318
let Read = [{ node->hasTrailingReturn() }];
319319
}
320+
def : Property<"cfiUncheckedCallee", Bool> {
321+
let Read = [{ node->hasCFIUncheckedCallee() }];
322+
}
320323
def : Property<"methodQualifiers", Qualifiers> {
321324
let Read = [{ node->getMethodQuals() }];
322325
}
@@ -353,6 +356,7 @@ let Class = FunctionProtoType in {
353356
epi.ExtInfo = extInfo;
354357
epi.Variadic = variadic;
355358
epi.HasTrailingReturn = trailingReturn;
359+
epi.CFIUncheckedCallee = cfiUncheckedCallee;
356360
epi.TypeQuals = methodQualifiers;
357361
epi.RefQualifier = refQualifier;
358362
epi.ExceptionSpec = exceptionSpecifier;

clang/include/clang/Basic/Attr.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3117,6 +3117,11 @@ def NoDeref : TypeAttr {
31173117
let Documentation = [NoDerefDocs];
31183118
}
31193119

3120+
def CFIUncheckedCallee : TypeAttr {
3121+
let Spellings = [Clang<"cfi_unchecked_callee">];
3122+
let Documentation = [CFIUncheckedCalleeDocs];
3123+
}
3124+
31203125
def ReqdWorkGroupSize : InheritableAttr {
31213126
// Does not have a [[]] spelling because it is an OpenCL-related attribute.
31223127
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/DiagnosticSemaKinds.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12711,6 +12711,11 @@ def warn_noderef_on_non_pointer_or_array : Warning<
1271112711
def warn_noderef_to_dereferenceable_pointer : Warning<
1271212712
"casting to dereferenceable pointer removes 'noderef' attribute">, InGroup<NoDeref>;
1271312713

12714+
def warn_cast_discards_cfi_unchecked_callee
12715+
: Warning<"implicit conversion from %0 to %1 discards "
12716+
"'cfi_unchecked_callee' attribute">,
12717+
InGroup<DiagGroup<"cfi-unchecked-callee">>;
12718+
1271412719
def err_builtin_launder_invalid_arg : Error<
1271512720
"%select{non-pointer|function pointer|void pointer}0 argument to "
1271612721
"'__builtin_launder' is not allowed">;

clang/include/clang/Sema/Sema.h

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2652,6 +2652,16 @@ class Sema final : public SemaBase {
26522652
/// void*).
26532653
void DiscardMisalignedMemberAddress(const Type *T, Expr *E);
26542654

2655+
/// Returns true if `From` is a function or pointer to a function with the
2656+
/// `cfi_unchecked_callee` attribute but `To` is a function or pointer to
2657+
/// function without this attribute.
2658+
bool DiscardingCFIUncheckedCallee(QualType From, QualType To) const;
2659+
2660+
/// Returns true if `From` is a function or pointer to a function without the
2661+
/// `cfi_unchecked_callee` attribute but `To` is a function or pointer to
2662+
/// function with this attribute.
2663+
bool AddingCFIUncheckedCallee(QualType From, QualType To) const;
2664+
26552665
/// This function calls Action when it determines that E designates a
26562666
/// misaligned member due to the packed attribute. This is used to emit
26572667
/// local diagnostics like in reference binding.
@@ -10262,9 +10272,15 @@ class Sema final : public SemaBase {
1026210272
bool CStyle, bool &ObjCLifetimeConversion);
1026310273

1026410274
/// Determine whether the conversion from FromType to ToType is a valid
10265-
/// conversion that strips "noexcept" or "noreturn" off the nested function
10266-
/// type.
10267-
bool IsFunctionConversion(QualType FromType, QualType ToType) const;
10275+
/// conversion that strips "noexcept" or "noreturn" or "cfi_unchecked_callee"
10276+
/// off the nested function type. This also checks if "cfi_unchecked_callee"
10277+
/// was added to the function type. If "cfi_unchecked_callee" is added and
10278+
/// `AddingCFIUncheckedCallee` is provided, it will be set to true. The same
10279+
/// thing applies for `DiscardingCFIUncheckedCallee` if the attribute is
10280+
/// discarded.
10281+
bool IsFunctionConversion(QualType FromType, QualType ToType,
10282+
bool *DiscardingCFIUncheckedCallee = nullptr,
10283+
bool *AddingCFIUncheckedCallee = nullptr) const;
1026810284

1026910285
/// Same as `IsFunctionConversion`, but if this would return true, it sets
1027010286
/// `ResultTy` to `ToType`.

clang/lib/AST/Type.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3572,6 +3572,12 @@ QualType QualType::getNonLValueExprType(const ASTContext &Context) const {
35723572
return *this;
35733573
}
35743574

3575+
bool FunctionType::getCFIUncheckedCalleeAttr() const {
3576+
if (const auto *FPT = getAs<FunctionProtoType>())
3577+
return FPT->hasCFIUncheckedCallee();
3578+
return false;
3579+
}
3580+
35753581
StringRef FunctionType::getNameForCallConv(CallingConv CC) {
35763582
switch (CC) {
35773583
case CC_C:
@@ -3663,6 +3669,7 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
36633669
FunctionTypeBits.HasExtParameterInfos = !!epi.ExtParameterInfos;
36643670
FunctionTypeBits.Variadic = epi.Variadic;
36653671
FunctionTypeBits.HasTrailingReturn = epi.HasTrailingReturn;
3672+
FunctionTypeBits.CFIUncheckedCallee = epi.CFIUncheckedCallee;
36663673

36673674
if (epi.requiresFunctionProtoTypeExtraBitfields()) {
36683675
FunctionTypeBits.HasExtraBitfields = true;
@@ -3930,6 +3937,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
39303937

39313938
ID.AddInteger((EffectCount << 3) | (HasConds << 2) |
39323939
(epi.AArch64SMEAttributes << 1) | epi.HasTrailingReturn);
3940+
ID.AddInteger(epi.CFIUncheckedCallee);
39333941

39343942
for (unsigned Idx = 0; Idx != EffectCount; ++Idx) {
39353943
ID.AddInteger(epi.FunctionEffects.Effects[Idx].toOpaqueInt32());

clang/lib/AST/TypePrinter.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,6 +1050,9 @@ void TypePrinter::printFunctionProtoAfter(const FunctionProtoType *T,
10501050
OS << "))";
10511051
}
10521052

1053+
if (T->hasCFIUncheckedCallee())
1054+
OS << " __attribute__((cfi_unchecked_callee))";
1055+
10531056
if (T->hasTrailingReturn()) {
10541057
OS << " -> ";
10551058
print(T->getReturnType(), OS, StringRef());
@@ -2090,6 +2093,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
20902093
case attr::NoDeref:
20912094
OS << "noderef";
20922095
break;
2096+
case attr::CFIUncheckedCallee:
2097+
OS << "cfi_unchecked_callee";
2098+
break;
20932099
case attr::AcquireHandle:
20942100
OS << "acquire_handle";
20952101
break;

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3053,9 +3053,13 @@ static LValue EmitFunctionDeclLValue(CodeGenFunction &CGF, const Expr *E,
30533053
GlobalDecl GD) {
30543054
const FunctionDecl *FD = cast<FunctionDecl>(GD.getDecl());
30553055
llvm::Constant *V = CGF.CGM.getFunctionPointer(GD);
3056+
QualType ETy = E->getType();
3057+
if (ETy->isCFIUncheckedCalleeFunctionType()) {
3058+
if (auto *GV = dyn_cast<llvm::GlobalValue>(V))
3059+
V = llvm::NoCFIValue::get(GV);
3060+
}
30563061
CharUnits Alignment = CGF.getContext().getDeclAlign(FD);
3057-
return CGF.MakeAddrLValue(V, E->getType(), Alignment,
3058-
AlignmentSource::Decl);
3062+
return CGF.MakeAddrLValue(V, ETy, Alignment, AlignmentSource::Decl);
30593063
}
30603064

30613065
static LValue EmitCapturedFieldLValue(CodeGenFunction &CGF, const FieldDecl *FD,
@@ -6349,10 +6353,13 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType,
63496353
FD && FD->hasAttr<OpenCLKernelAttr>())
63506354
CGM.getTargetCodeGenInfo().setOCLKernelStubCallingConvention(FnType);
63516355

6356+
bool CFIUnchecked =
6357+
CalleeType->hasPointeeToToCFIUncheckedCalleeFunctionType();
6358+
63526359
// If we are checking indirect calls and this call is indirect, check that the
63536360
// function pointer is a member of the bit set for the function type.
63546361
if (SanOpts.has(SanitizerKind::CFIICall) &&
6355-
(!TargetDecl || !isa<FunctionDecl>(TargetDecl))) {
6362+
(!TargetDecl || !isa<FunctionDecl>(TargetDecl)) && !CFIUnchecked) {
63566363
SanitizerScope SanScope(this);
63576364
EmitSanitizerStatReport(llvm::SanStat_CFI_ICall);
63586365
ApplyDebugLocation ApplyTrapDI(

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.

0 commit comments

Comments
 (0)