Skip to content

Commit 9c0722d

Browse files
bwendlingAaronBallman
authored andcommitted
[Clang][attr] Add 'cfi_salt' attribute (llvm#141846)
The 'cfi_salt' attribute specifies a string literal that is used as a "salt" for Control-Flow Integrity (CFI) checks to distinguish between functions with the same type signature. This attribute can be applied to function declarations, function definitions, and function pointer typedefs. This attribute prevents function pointers from being replaced with pointers to functions that have a compatible type, which can be a CFI bypass vector. The attribute affects type compatibility during compilation and CFI hash generation during code generation. Attribute syntax: [[clang::cfi_salt("<salt_string>")]] GNU-style syntax: __attribute__((cfi_salt("<salt_string>"))) - The attribute takes a single string of non-NULL ASCII characters. - It only applies to function types; using it on a non-function type will generate an error. - All function declarations and the function definition must include the attribute and use identical salt values. Example usage: // Header file: #define __cfi_salt(S) __attribute__((cfi_salt(S))) // Convenient typedefs to avoid nested declarator syntax. typedef int (*fp_unsalted_t)(void); typedef int (*fp_salted_t)(void) __cfi_salt("pepper"); struct widget_ops { fp_unsalted_t init; // Regular CFI. fp_salted_t exec; // Salted CFI. fp_unsalted_t teardown; // Regular CFI. }; // bar.c file: static int bar_init(void) { ... } static int bar_salted_exec(void) __cfi_salt("pepper") { ... } static int bar_teardown(void) { ... } static struct widget_generator _generator = { .init = bar_init, .exec = bar_salted_exec, .teardown = bar_teardown, }; struct widget_generator *widget_gen = _generator; // 2nd .c file: int generate_a_widget(void) { int ret; // Called with non-salted CFI. ret = widget_gen.init(); if (ret) return ret; // Called with salted CFI. ret = widget_gen.exec(); if (ret) return ret; // Called with non-salted CFI. return widget_gen.teardown(); } Link: ClangBuiltLinux/linux#1736 Link: KSPP/linux#365 --------- Signed-off-by: Bill Wendling <[email protected]> Co-authored-by: Aaron Ballman <[email protected]>
1 parent 855fe3b commit 9c0722d

File tree

13 files changed

+482
-20
lines changed

13 files changed

+482
-20
lines changed

clang/include/clang/AST/Type.h

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4687,6 +4687,9 @@ class FunctionType : public Type {
46874687
/// [implimits] 8 bits would be enough here.
46884688
unsigned NumExceptionType : 10;
46894689

4690+
LLVM_PREFERRED_TYPE(bool)
4691+
unsigned HasExtraAttributeInfo : 1;
4692+
46904693
LLVM_PREFERRED_TYPE(bool)
46914694
unsigned HasArmTypeAttributes : 1;
46924695

@@ -4695,14 +4698,26 @@ class FunctionType : public Type {
46954698
unsigned NumFunctionEffects : 4;
46964699

46974700
FunctionTypeExtraBitfields()
4698-
: NumExceptionType(0), HasArmTypeAttributes(false),
4699-
EffectsHaveConditions(false), NumFunctionEffects(0) {}
4701+
: NumExceptionType(0), HasExtraAttributeInfo(false),
4702+
HasArmTypeAttributes(false), EffectsHaveConditions(false),
4703+
NumFunctionEffects(0) {}
4704+
};
4705+
4706+
/// A holder for extra information from attributes which aren't part of an
4707+
/// \p AttributedType.
4708+
struct alignas(void *) FunctionTypeExtraAttributeInfo {
4709+
/// A CFI "salt" that differentiates functions with the same prototype.
4710+
StringRef CFISalt;
4711+
4712+
operator bool() const { return !CFISalt.empty(); }
4713+
4714+
void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddString(CFISalt); }
47004715
};
47014716

47024717
/// The AArch64 SME ACLE (Arm C/C++ Language Extensions) define a number
47034718
/// of function type attributes that can be set on function types, including
47044719
/// function pointers.
4705-
enum AArch64SMETypeAttributes : unsigned {
4720+
enum AArch64SMETypeAttributes : uint16_t {
47064721
SME_NormalFunction = 0,
47074722
SME_PStateSMEnabledMask = 1 << 0,
47084723
SME_PStateSMCompatibleMask = 1 << 1,
@@ -4732,11 +4747,11 @@ class FunctionType : public Type {
47324747
};
47334748

47344749
static ArmStateValue getArmZAState(unsigned AttrBits) {
4735-
return (ArmStateValue)((AttrBits & SME_ZAMask) >> SME_ZAShift);
4750+
return static_cast<ArmStateValue>((AttrBits & SME_ZAMask) >> SME_ZAShift);
47364751
}
47374752

47384753
static ArmStateValue getArmZT0State(unsigned AttrBits) {
4739-
return (ArmStateValue)((AttrBits & SME_ZT0Mask) >> SME_ZT0Shift);
4754+
return static_cast<ArmStateValue>((AttrBits & SME_ZT0Mask) >> SME_ZT0Shift);
47404755
}
47414756

47424757
/// A holder for Arm type attributes as described in the Arm C/C++
@@ -4745,6 +4760,7 @@ class FunctionType : public Type {
47454760
struct alignas(void *) FunctionTypeArmAttributes {
47464761
/// Any AArch64 SME ACLE type attributes that need to be propagated
47474762
/// on declarations and function pointers.
4763+
LLVM_PREFERRED_TYPE(AArch64SMETypeAttributes)
47484764
unsigned AArch64SMEAttributes : 9;
47494765

47504766
FunctionTypeArmAttributes() : AArch64SMEAttributes(SME_NormalFunction) {}
@@ -5226,6 +5242,7 @@ class FunctionProtoType final
52265242
private llvm::TrailingObjects<
52275243
FunctionProtoType, QualType, SourceLocation,
52285244
FunctionType::FunctionTypeExtraBitfields,
5245+
FunctionType::FunctionTypeExtraAttributeInfo,
52295246
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
52305247
Expr *, FunctionDecl *, FunctionType::ExtParameterInfo, Qualifiers,
52315248
FunctionEffect, EffectConditionExpr> {
@@ -5315,19 +5332,22 @@ class FunctionProtoType final
53155332
/// the various bits of extra information about a function prototype.
53165333
struct ExtProtoInfo {
53175334
FunctionType::ExtInfo ExtInfo;
5335+
Qualifiers TypeQuals;
5336+
RefQualifierKind RefQualifier = RQ_None;
5337+
ExceptionSpecInfo ExceptionSpec;
5338+
const ExtParameterInfo *ExtParameterInfos = nullptr;
5339+
SourceLocation EllipsisLoc;
5340+
FunctionEffectsRef FunctionEffects;
5341+
FunctionTypeExtraAttributeInfo ExtraAttributeInfo;
5342+
53185343
LLVM_PREFERRED_TYPE(bool)
53195344
unsigned Variadic : 1;
53205345
LLVM_PREFERRED_TYPE(bool)
53215346
unsigned HasTrailingReturn : 1;
53225347
LLVM_PREFERRED_TYPE(bool)
53235348
unsigned CFIUncheckedCallee : 1;
5349+
LLVM_PREFERRED_TYPE(AArch64SMETypeAttributes)
53245350
unsigned AArch64SMEAttributes : 9;
5325-
Qualifiers TypeQuals;
5326-
RefQualifierKind RefQualifier = RQ_None;
5327-
ExceptionSpecInfo ExceptionSpec;
5328-
const ExtParameterInfo *ExtParameterInfos = nullptr;
5329-
SourceLocation EllipsisLoc;
5330-
FunctionEffectsRef FunctionEffects;
53315351

53325352
ExtProtoInfo()
53335353
: Variadic(false), HasTrailingReturn(false), CFIUncheckedCallee(false),
@@ -5352,13 +5372,18 @@ class FunctionProtoType final
53525372
bool requiresFunctionProtoTypeExtraBitfields() const {
53535373
return ExceptionSpec.Type == EST_Dynamic ||
53545374
requiresFunctionProtoTypeArmAttributes() ||
5375+
requiresFunctionProtoTypeExtraAttributeInfo() ||
53555376
!FunctionEffects.empty();
53565377
}
53575378

53585379
bool requiresFunctionProtoTypeArmAttributes() const {
53595380
return AArch64SMEAttributes != SME_NormalFunction;
53605381
}
53615382

5383+
bool requiresFunctionProtoTypeExtraAttributeInfo() const {
5384+
return static_cast<bool>(ExtraAttributeInfo);
5385+
}
5386+
53625387
void setArmSMEAttribute(AArch64SMETypeAttributes Kind, bool Enable = true) {
53635388
if (Enable)
53645389
AArch64SMEAttributes |= Kind;
@@ -5384,6 +5409,11 @@ class FunctionProtoType final
53845409
return hasExtraBitfields();
53855410
}
53865411

5412+
unsigned
5413+
numTrailingObjects(OverloadToken<FunctionTypeExtraAttributeInfo>) const {
5414+
return hasExtraAttributeInfo();
5415+
}
5416+
53875417
unsigned numTrailingObjects(OverloadToken<ExceptionType>) const {
53885418
return getExceptionSpecSize().NumExceptionType;
53895419
}
@@ -5476,6 +5506,12 @@ class FunctionProtoType final
54765506

54775507
}
54785508

5509+
bool hasExtraAttributeInfo() const {
5510+
return FunctionTypeBits.HasExtraBitfields &&
5511+
getTrailingObjects<FunctionTypeExtraBitfields>()
5512+
->HasExtraAttributeInfo;
5513+
}
5514+
54795515
bool hasArmTypeAttributes() const {
54805516
return FunctionTypeBits.HasExtraBitfields &&
54815517
getTrailingObjects<FunctionTypeExtraBitfields>()
@@ -5509,6 +5545,7 @@ class FunctionProtoType final
55095545
EPI.TypeQuals = getMethodQuals();
55105546
EPI.RefQualifier = getRefQualifier();
55115547
EPI.ExtParameterInfos = getExtParameterInfosOrNull();
5548+
EPI.ExtraAttributeInfo = getExtraAttributeInfo();
55125549
EPI.AArch64SMEAttributes = getAArch64SMEAttributes();
55135550
EPI.FunctionEffects = getFunctionEffects();
55145551
return EPI;
@@ -5696,6 +5733,13 @@ class FunctionProtoType final
56965733
return getTrailingObjects<ExtParameterInfo>();
56975734
}
56985735

5736+
/// Return the extra attribute information.
5737+
FunctionTypeExtraAttributeInfo getExtraAttributeInfo() const {
5738+
if (hasExtraAttributeInfo())
5739+
return *getTrailingObjects<FunctionTypeExtraAttributeInfo>();
5740+
return FunctionTypeExtraAttributeInfo();
5741+
}
5742+
56995743
/// Return a bitmask describing the SME attributes on the function type, see
57005744
/// AArch64SMETypeAttributes for their values.
57015745
unsigned getAArch64SMEAttributes() const {

clang/include/clang/Basic/Attr.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3922,6 +3922,14 @@ def CFICanonicalJumpTable : InheritableAttr {
39223922
let SimpleHandler = 1;
39233923
}
39243924

3925+
def CFISalt : TypeAttr {
3926+
let Spellings = [Clang<"cfi_salt">];
3927+
let Args = [StringArgument<"Salt">];
3928+
let Subjects = SubjectList<[FunctionLike], ErrorDiag>;
3929+
let Documentation = [CFISaltDocs];
3930+
let LangOpts = [COnly];
3931+
}
3932+
39253933
// C/C++ Thread safety attributes (e.g. for deadlock, data race checking)
39263934
// Not all of these attributes will be given a [[]] spelling. The attributes
39273935
// which require access to function parameter names cannot use the [[]] spelling

clang/include/clang/Basic/AttrDocs.td

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3646,6 +3646,99 @@ make the function's CFI jump table canonical. See :ref:`the CFI documentation
36463646
}];
36473647
}
36483648

3649+
def CFISaltDocs : Documentation {
3650+
let Category = DocCatFunction;
3651+
let Heading = "cfi_salt";
3652+
let Label = "langext-cfi_salt";
3653+
let Content = [{
3654+
The ``cfi_salt`` attribute specifies a string literal that is used as a salt
3655+
for Control-Flow Integrity (CFI) checks to distinguish between functions with
3656+
the same type signature. This attribute can be applied to function declarations,
3657+
function definitions, and function pointer typedefs.
3658+
3659+
The attribute prevents function pointers from being replaced with pointers to
3660+
functions that have a compatible type, which can be a CFI bypass vector.
3661+
3662+
**Syntax:**
3663+
3664+
* GNU-style: ``__attribute__((cfi_salt("<salt_string>")))``
3665+
* C++11-style: ``[[clang::cfi_salt("<salt_string>")]]``
3666+
3667+
**Usage:**
3668+
3669+
The attribute takes a single string literal argument that serves as the salt.
3670+
Functions or function types with different salt values will have different CFI
3671+
hashes, even if they have identical type signatures.
3672+
3673+
**Motivation:**
3674+
3675+
In large codebases like the Linux kernel, there are often hundreds of functions
3676+
with identical type signatures that are called indirectly:
3677+
3678+
.. code-block::
3679+
3680+
1662 functions with void (*)(void)
3681+
1179 functions with int (*)(void)
3682+
...
3683+
3684+
By salting the CFI hashes, you can make CFI more robust by ensuring that
3685+
functions intended for different purposes have distinct CFI identities.
3686+
3687+
**Type Compatibility:**
3688+
3689+
* Functions with different salt values are considered to have incompatible types
3690+
* Function pointers with different salt values cannot be assigned to each other
3691+
* All declarations of the same function must use the same salt value
3692+
3693+
**Example:**
3694+
3695+
.. code-block:: c
3696+
3697+
// Header file - define convenience macros
3698+
#define __cfi_salt(s) __attribute__((cfi_salt(s)))
3699+
3700+
// Typedef for regular function pointers
3701+
typedef int (*fptr_t)(void);
3702+
3703+
// Typedef for salted function pointers
3704+
typedef int (*fptr_salted_t)(void) __cfi_salt("pepper");
3705+
3706+
struct widget_ops {
3707+
fptr_t init; // Regular CFI
3708+
fptr_salted_t exec; // Salted CFI
3709+
fptr_t cleanup; // Regular CFI
3710+
};
3711+
3712+
// Function implementations
3713+
static int widget_init(void) { return 0; }
3714+
static int widget_exec(void) __cfi_salt("pepper") { return 1; }
3715+
static int widget_cleanup(void) { return 0; }
3716+
3717+
static struct widget_ops ops = {
3718+
.init = widget_init, // OK - compatible types
3719+
.exec = widget_exec, // OK - both use "pepper" salt
3720+
.cleanup = widget_cleanup // OK - compatible types
3721+
};
3722+
3723+
// Using C++11 attribute syntax
3724+
void secure_callback(void) [[clang::cfi_salt("secure")]];
3725+
3726+
// This would cause a compilation error:
3727+
// fptr_t bad_ptr = widget_exec; // Error: incompatible types
3728+
3729+
**Notes:**
3730+
3731+
* The salt string can contain non-NULL ASCII characters, including spaces and
3732+
quotes
3733+
* This attribute only applies to function types; using it on non-function
3734+
types will generate a warning
3735+
* All declarations and definitions of the same function must use identical
3736+
salt values
3737+
* The attribute affects type compatibility during compilation and CFI hash
3738+
generation during code generation
3739+
}];
3740+
}
3741+
36493742
def DocCatTypeSafety : DocumentationCategory<"Type Safety Checking"> {
36503743
let Content = [{
36513744
Clang supports additional attributes to enable checking type safety properties

clang/lib/AST/ASTContext.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5128,10 +5128,12 @@ QualType ASTContext::getFunctionTypeInternal(
51285128
EPI.ExceptionSpec.Type, EPI.ExceptionSpec.Exceptions.size());
51295129
size_t Size = FunctionProtoType::totalSizeToAlloc<
51305130
QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
5131+
FunctionType::FunctionTypeExtraAttributeInfo,
51315132
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
51325133
Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, Qualifiers,
51335134
FunctionEffect, EffectConditionExpr>(
51345135
NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
5136+
EPI.requiresFunctionProtoTypeExtraAttributeInfo(),
51355137
EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
51365138
ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
51375139
EPI.ExtParameterInfos ? NumArgs : 0,
@@ -11552,6 +11554,11 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
1155211554
if (lproto->getMethodQuals() != rproto->getMethodQuals())
1155311555
return {};
1155411556

11557+
// Function protos with different 'cfi_salt' values aren't compatible.
11558+
if (lproto->getExtraAttributeInfo().CFISalt !=
11559+
rproto->getExtraAttributeInfo().CFISalt)
11560+
return {};
11561+
1155511562
// Function effects are handled similarly to noreturn, see above.
1155611563
FunctionEffectsRef LHSFX = lproto->getFunctionEffects();
1155711564
FunctionEffectsRef RHSFX = rproto->getFunctionEffects();

clang/lib/AST/Type.cpp

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3751,6 +3751,16 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
37513751
FunctionTypeBits.HasExtraBitfields = false;
37523752
}
37533753

3754+
// Propagate any extra attribute information.
3755+
if (epi.requiresFunctionProtoTypeExtraAttributeInfo()) {
3756+
auto &ExtraAttrInfo = *getTrailingObjects<FunctionTypeExtraAttributeInfo>();
3757+
ExtraAttrInfo.CFISalt = epi.ExtraAttributeInfo.CFISalt;
3758+
3759+
// Also set the bit in FunctionTypeExtraBitfields.
3760+
auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>();
3761+
ExtraBits.HasExtraAttributeInfo = true;
3762+
}
3763+
37543764
if (epi.requiresFunctionProtoTypeArmAttributes()) {
37553765
auto &ArmTypeAttrs = *getTrailingObjects<FunctionTypeArmAttributes>();
37563766
ArmTypeAttrs = FunctionTypeArmAttributes();
@@ -3968,7 +3978,8 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
39683978
// This is followed by the ext info:
39693979
// int
39703980
// Finally we have a trailing return type flag (bool)
3971-
// combined with AArch64 SME Attributes, to save space:
3981+
// combined with AArch64 SME Attributes and extra attribute info, to save
3982+
// space:
39723983
// int
39733984
// combined with any FunctionEffects
39743985
//
@@ -4003,6 +4014,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
40034014
}
40044015

40054016
epi.ExtInfo.Profile(ID);
4017+
epi.ExtraAttributeInfo.Profile(ID);
40064018

40074019
unsigned EffectCount = epi.FunctionEffects.size();
40084020
bool HasConds = !epi.FunctionEffects.Conditions.empty();

clang/lib/AST/TypePrinter.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2154,6 +2154,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
21542154
case attr::ExtVectorType:
21552155
OS << "ext_vector_type";
21562156
break;
2157+
case attr::CFISalt:
2158+
OS << "cfi_salt(\"" << cast<CFISaltAttr>(T->getAttr())->getSalt() << "\")";
2159+
break;
21572160
}
21582161
OS << "))";
21592162
}

clang/lib/CodeGen/CodeGenFunction.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2903,10 +2903,16 @@ void CodeGenFunction::EmitSanitizerStatReport(llvm::SanitizerStatKind SSK) {
29032903

29042904
void CodeGenFunction::EmitKCFIOperandBundle(
29052905
const CGCallee &Callee, SmallVectorImpl<llvm::OperandBundleDef> &Bundles) {
2906-
const FunctionProtoType *FP =
2907-
Callee.getAbstractInfo().getCalleeFunctionProtoType();
2908-
if (FP)
2909-
Bundles.emplace_back("kcfi", CGM.CreateKCFITypeId(FP->desugar()));
2906+
const CGCalleeInfo &CI = Callee.getAbstractInfo();
2907+
const FunctionProtoType *FP = CI.getCalleeFunctionProtoType();
2908+
if (!FP)
2909+
return;
2910+
2911+
StringRef Salt;
2912+
if (const auto &Info = FP->getExtraAttributeInfo())
2913+
Salt = Info.CFISalt;
2914+
2915+
Bundles.emplace_back("kcfi", CGM.CreateKCFITypeId(FP->desugar(), Salt));
29102916
}
29112917

29122918
llvm::Value *

0 commit comments

Comments
 (0)