Skip to content

[Clang][attr] Add 'cfi_salt' attribute #141846

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 36 commits into from
Aug 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bfdf16c
[Clang][attr] Add cfi_salt attribute
bwendling Apr 23, 2025
04d2c0b
Don't use AttributedType. Instead add it as a trailing object to the …
bwendling Jun 2, 2025
94d10c5
Merge branch 'main' into kcfi-salt
bwendling Jun 5, 2025
bbce666
Merge branch 'llvm:main' into kcfi-salt
bwendling Jun 25, 2025
e36bc7c
Merge branch 'llvm:main' into kcfi-salt
bwendling Jun 26, 2025
b0c8a2e
Merge branch 'llvm:main' into kcfi-salt
bwendling Jul 2, 2025
9e164e5
Merge branch 'llvm:main' into kcfi-salt
bwendling Jul 7, 2025
eef5f43
Reserve attribute to C-only and rename to 'kcfi_salt', indicating tha…
bwendling Jul 8, 2025
46d0edd
Alphabetize.
bwendling Jul 8, 2025
f4a54d9
Reformat.
bwendling Jul 8, 2025
e9aaaf5
Use correct preferred type and fix the attribute documentation.
bwendling Jul 17, 2025
0b13d3a
Merge branch 'llvm:main' into kcfi-salt
bwendling Jul 21, 2025
cec156a
Rename 'kcfi_salt' to 'cfi_salt' to indicate that it's not just for t…
bwendling Jul 23, 2025
44950cc
Reject function protos that have different cfi_salt values.
bwendling Jul 23, 2025
514523b
fixup! Reject function protos that have different cfi_salt values.
bwendling Jul 30, 2025
f74c5ea
Remove C++ test. Try to get along with only a 'TypeAttr' instead of '…
bwendling Jul 30, 2025
527297f
Use the modern syntax for the attribute. Remove the C++ mangled name …
bwendling Jul 30, 2025
2df94a9
Re-add attributes on function vars and decls.
bwendling Aug 1, 2025
66cb83b
Fix testcase.
bwendling Aug 1, 2025
516aa05
Add extra test.
bwendling Aug 1, 2025
1b1d3ab
Enhance the attribute docs.
bwendling Aug 1, 2025
6d00a7e
Use 'FunctionList' for the Subjects.
bwendling Aug 1, 2025
2e9022d
Use RST instead of MD.
bwendling Aug 1, 2025
7f2de1c
Improve docs. It was totally just me and not Claude rewriting it.
bwendling Aug 1, 2025
44c179d
Add missing backtick.
bwendling Aug 1, 2025
029ee8e
Add tests for a salt that's empty.
bwendling Aug 2, 2025
06e43bd
Change number of arguments check. Add test for it. And move the diagn…
bwendling Aug 4, 2025
132995f
Specify that we accept non-NULL ASCII strings.
bwendling Aug 11, 2025
cd975ea
Add a diagnostic for K&R-style functions without a prototype.
bwendling Aug 11, 2025
abe3bde
Improve the docs by explaining why the salt improves CFI safety.
bwendling Aug 12, 2025
c2dba02
Update clang/test/Sema/attr-cfi-salt.c
bwendling Aug 14, 2025
95698c2
Return 'true' when the argument type isn't a string.
bwendling Aug 14, 2025
09a446a
Merge branch 'main' into kcfi-salt
bwendling Aug 14, 2025
ae91164
Merge branch 'main' into kcfi-salt
bwendling Aug 14, 2025
9bd2c95
Reformat
bwendling Aug 14, 2025
9700cac
Add back accidentally removed code.
bwendling Aug 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 55 additions & 11 deletions clang/include/clang/AST/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -4687,6 +4687,9 @@ class FunctionType : public Type {
/// [implimits] 8 bits would be enough here.
unsigned NumExceptionType : 10;

LLVM_PREFERRED_TYPE(bool)
unsigned HasExtraAttributeInfo : 1;

LLVM_PREFERRED_TYPE(bool)
unsigned HasArmTypeAttributes : 1;

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

FunctionTypeExtraBitfields()
: NumExceptionType(0), HasArmTypeAttributes(false),
EffectsHaveConditions(false), NumFunctionEffects(0) {}
: NumExceptionType(0), HasExtraAttributeInfo(false),
HasArmTypeAttributes(false), EffectsHaveConditions(false),
NumFunctionEffects(0) {}
};

/// A holder for extra information from attributes which aren't part of an
/// \p AttributedType.
struct alignas(void *) FunctionTypeExtraAttributeInfo {
/// A CFI "salt" that differentiates functions with the same prototype.
StringRef CFISalt;

operator bool() const { return !CFISalt.empty(); }

void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddString(CFISalt); }
};

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

static ArmStateValue getArmZAState(unsigned AttrBits) {
return (ArmStateValue)((AttrBits & SME_ZAMask) >> SME_ZAShift);
return static_cast<ArmStateValue>((AttrBits & SME_ZAMask) >> SME_ZAShift);
}

static ArmStateValue getArmZT0State(unsigned AttrBits) {
return (ArmStateValue)((AttrBits & SME_ZT0Mask) >> SME_ZT0Shift);
return static_cast<ArmStateValue>((AttrBits & SME_ZT0Mask) >> SME_ZT0Shift);
}

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

FunctionTypeArmAttributes() : AArch64SMEAttributes(SME_NormalFunction) {}
Expand Down Expand Up @@ -5226,6 +5242,7 @@ class FunctionProtoType final
private llvm::TrailingObjects<
FunctionProtoType, QualType, SourceLocation,
FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeExtraAttributeInfo,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
Expr *, FunctionDecl *, FunctionType::ExtParameterInfo, Qualifiers,
FunctionEffect, EffectConditionExpr> {
Expand Down Expand Up @@ -5315,19 +5332,22 @@ class FunctionProtoType final
/// the various bits of extra information about a function prototype.
struct ExtProtoInfo {
FunctionType::ExtInfo ExtInfo;
Qualifiers TypeQuals;
RefQualifierKind RefQualifier = RQ_None;
ExceptionSpecInfo ExceptionSpec;
const ExtParameterInfo *ExtParameterInfos = nullptr;
SourceLocation EllipsisLoc;
FunctionEffectsRef FunctionEffects;
FunctionTypeExtraAttributeInfo ExtraAttributeInfo;

LLVM_PREFERRED_TYPE(bool)
unsigned Variadic : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned HasTrailingReturn : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned CFIUncheckedCallee : 1;
LLVM_PREFERRED_TYPE(AArch64SMETypeAttributes)
unsigned AArch64SMEAttributes : 9;
Qualifiers TypeQuals;
RefQualifierKind RefQualifier = RQ_None;
ExceptionSpecInfo ExceptionSpec;
const ExtParameterInfo *ExtParameterInfos = nullptr;
SourceLocation EllipsisLoc;
FunctionEffectsRef FunctionEffects;

ExtProtoInfo()
: Variadic(false), HasTrailingReturn(false), CFIUncheckedCallee(false),
Expand All @@ -5352,13 +5372,18 @@ class FunctionProtoType final
bool requiresFunctionProtoTypeExtraBitfields() const {
return ExceptionSpec.Type == EST_Dynamic ||
requiresFunctionProtoTypeArmAttributes() ||
requiresFunctionProtoTypeExtraAttributeInfo() ||
!FunctionEffects.empty();
}

bool requiresFunctionProtoTypeArmAttributes() const {
return AArch64SMEAttributes != SME_NormalFunction;
}

bool requiresFunctionProtoTypeExtraAttributeInfo() const {
return static_cast<bool>(ExtraAttributeInfo);
}

void setArmSMEAttribute(AArch64SMETypeAttributes Kind, bool Enable = true) {
if (Enable)
AArch64SMEAttributes |= Kind;
Expand All @@ -5384,6 +5409,11 @@ class FunctionProtoType final
return hasExtraBitfields();
}

unsigned
numTrailingObjects(OverloadToken<FunctionTypeExtraAttributeInfo>) const {
return hasExtraAttributeInfo();
}

unsigned numTrailingObjects(OverloadToken<ExceptionType>) const {
return getExceptionSpecSize().NumExceptionType;
}
Expand Down Expand Up @@ -5476,6 +5506,12 @@ class FunctionProtoType final

}

bool hasExtraAttributeInfo() const {
return FunctionTypeBits.HasExtraBitfields &&
getTrailingObjects<FunctionTypeExtraBitfields>()
->HasExtraAttributeInfo;
}

bool hasArmTypeAttributes() const {
return FunctionTypeBits.HasExtraBitfields &&
getTrailingObjects<FunctionTypeExtraBitfields>()
Expand Down Expand Up @@ -5509,6 +5545,7 @@ class FunctionProtoType final
EPI.TypeQuals = getMethodQuals();
EPI.RefQualifier = getRefQualifier();
EPI.ExtParameterInfos = getExtParameterInfosOrNull();
EPI.ExtraAttributeInfo = getExtraAttributeInfo();
EPI.AArch64SMEAttributes = getAArch64SMEAttributes();
EPI.FunctionEffects = getFunctionEffects();
return EPI;
Expand Down Expand Up @@ -5696,6 +5733,13 @@ class FunctionProtoType final
return getTrailingObjects<ExtParameterInfo>();
}

/// Return the extra attribute information.
FunctionTypeExtraAttributeInfo getExtraAttributeInfo() const {
if (hasExtraAttributeInfo())
return *getTrailingObjects<FunctionTypeExtraAttributeInfo>();
return FunctionTypeExtraAttributeInfo();
}

/// Return a bitmask describing the SME attributes on the function type, see
/// AArch64SMETypeAttributes for their values.
unsigned getAArch64SMEAttributes() const {
Expand Down
8 changes: 8 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -3922,6 +3922,14 @@ def CFICanonicalJumpTable : InheritableAttr {
let SimpleHandler = 1;
}

def CFISalt : TypeAttr {
let Spellings = [Clang<"cfi_salt">];
let Args = [StringArgument<"Salt">];
let Subjects = SubjectList<[FunctionLike], ErrorDiag>;
let Documentation = [CFISaltDocs];
let LangOpts = [COnly];
}

// C/C++ Thread safety attributes (e.g. for deadlock, data race checking)
// Not all of these attributes will be given a [[]] spelling. The attributes
// which require access to function parameter names cannot use the [[]] spelling
Expand Down
93 changes: 93 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -3646,6 +3646,99 @@ make the function's CFI jump table canonical. See :ref:`the CFI documentation
}];
}

def CFISaltDocs : Documentation {
let Category = DocCatFunction;
let Heading = "cfi_salt";
let Label = "langext-cfi_salt";
let Content = [{
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.

The attribute prevents function pointers from being replaced with pointers to
functions that have a compatible type, which can be a CFI bypass vector.

**Syntax:**

* GNU-style: ``__attribute__((cfi_salt("<salt_string>")))``
* C++11-style: ``[[clang::cfi_salt("<salt_string>")]]``

**Usage:**

The attribute takes a single string literal argument that serves as the salt.
Functions or function types with different salt values will have different CFI
hashes, even if they have identical type signatures.

**Motivation:**

In large codebases like the Linux kernel, there are often hundreds of functions
with identical type signatures that are called indirectly:

.. code-block::

1662 functions with void (*)(void)
1179 functions with int (*)(void)
...

By salting the CFI hashes, you can make CFI more robust by ensuring that
functions intended for different purposes have distinct CFI identities.

**Type Compatibility:**

* Functions with different salt values are considered to have incompatible types
* Function pointers with different salt values cannot be assigned to each other
* All declarations of the same function must use the same salt value

**Example:**

.. code-block:: c

// Header file - define convenience macros
#define __cfi_salt(s) __attribute__((cfi_salt(s)))

// Typedef for regular function pointers
typedef int (*fptr_t)(void);

// Typedef for salted function pointers
typedef int (*fptr_salted_t)(void) __cfi_salt("pepper");

struct widget_ops {
fptr_t init; // Regular CFI
fptr_salted_t exec; // Salted CFI
fptr_t cleanup; // Regular CFI
};

// Function implementations
static int widget_init(void) { return 0; }
static int widget_exec(void) __cfi_salt("pepper") { return 1; }
static int widget_cleanup(void) { return 0; }

static struct widget_ops ops = {
.init = widget_init, // OK - compatible types
.exec = widget_exec, // OK - both use "pepper" salt
.cleanup = widget_cleanup // OK - compatible types
};

// Using C++11 attribute syntax
void secure_callback(void) [[clang::cfi_salt("secure")]];

// This would cause a compilation error:
// fptr_t bad_ptr = widget_exec; // Error: incompatible types

**Notes:**

* The salt string can contain non-NULL ASCII characters, including spaces and
quotes
* This attribute only applies to function types; using it on non-function
types will generate a warning
* All declarations and definitions of the same function must use identical
salt values
* The attribute affects type compatibility during compilation and CFI hash
generation during code generation
}];
}

def DocCatTypeSafety : DocumentationCategory<"Type Safety Checking"> {
let Content = [{
Clang supports additional attributes to enable checking type safety properties
Expand Down
7 changes: 7 additions & 0 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5128,10 +5128,12 @@ QualType ASTContext::getFunctionTypeInternal(
EPI.ExceptionSpec.Type, EPI.ExceptionSpec.Exceptions.size());
size_t Size = FunctionProtoType::totalSizeToAlloc<
QualType, SourceLocation, FunctionType::FunctionTypeExtraBitfields,
FunctionType::FunctionTypeExtraAttributeInfo,
FunctionType::FunctionTypeArmAttributes, FunctionType::ExceptionType,
Expr *, FunctionDecl *, FunctionProtoType::ExtParameterInfo, Qualifiers,
FunctionEffect, EffectConditionExpr>(
NumArgs, EPI.Variadic, EPI.requiresFunctionProtoTypeExtraBitfields(),
EPI.requiresFunctionProtoTypeExtraAttributeInfo(),
EPI.requiresFunctionProtoTypeArmAttributes(), ESH.NumExceptionType,
ESH.NumExprPtr, ESH.NumFunctionDeclPtr,
EPI.ExtParameterInfos ? NumArgs : 0,
Expand Down Expand Up @@ -11552,6 +11554,11 @@ QualType ASTContext::mergeFunctionTypes(QualType lhs, QualType rhs,
if (lproto->getMethodQuals() != rproto->getMethodQuals())
return {};

// Function protos with different 'cfi_salt' values aren't compatible.
if (lproto->getExtraAttributeInfo().CFISalt !=
rproto->getExtraAttributeInfo().CFISalt)
return {};

// Function effects are handled similarly to noreturn, see above.
FunctionEffectsRef LHSFX = lproto->getFunctionEffects();
FunctionEffectsRef RHSFX = rproto->getFunctionEffects();
Expand Down
14 changes: 13 additions & 1 deletion clang/lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3751,6 +3751,16 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
FunctionTypeBits.HasExtraBitfields = false;
}

// Propagate any extra attribute information.
if (epi.requiresFunctionProtoTypeExtraAttributeInfo()) {
auto &ExtraAttrInfo = *getTrailingObjects<FunctionTypeExtraAttributeInfo>();
ExtraAttrInfo.CFISalt = epi.ExtraAttributeInfo.CFISalt;

// Also set the bit in FunctionTypeExtraBitfields.
auto &ExtraBits = *getTrailingObjects<FunctionTypeExtraBitfields>();
ExtraBits.HasExtraAttributeInfo = true;
}

if (epi.requiresFunctionProtoTypeArmAttributes()) {
auto &ArmTypeAttrs = *getTrailingObjects<FunctionTypeArmAttributes>();
ArmTypeAttrs = FunctionTypeArmAttributes();
Expand Down Expand Up @@ -3968,7 +3978,8 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
// This is followed by the ext info:
// int
// Finally we have a trailing return type flag (bool)
// combined with AArch64 SME Attributes, to save space:
// combined with AArch64 SME Attributes and extra attribute info, to save
// space:
// int
// combined with any FunctionEffects
//
Expand Down Expand Up @@ -4003,6 +4014,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result,
}

epi.ExtInfo.Profile(ID);
epi.ExtraAttributeInfo.Profile(ID);

unsigned EffectCount = epi.FunctionEffects.size();
bool HasConds = !epi.FunctionEffects.Conditions.empty();
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/AST/TypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2154,6 +2154,9 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::ExtVectorType:
OS << "ext_vector_type";
break;
case attr::CFISalt:
OS << "cfi_salt(\"" << cast<CFISaltAttr>(T->getAttr())->getSalt() << "\")";
break;
}
OS << "))";
}
Expand Down
14 changes: 10 additions & 4 deletions clang/lib/CodeGen/CodeGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2903,10 +2903,16 @@ void CodeGenFunction::EmitSanitizerStatReport(llvm::SanitizerStatKind SSK) {

void CodeGenFunction::EmitKCFIOperandBundle(
const CGCallee &Callee, SmallVectorImpl<llvm::OperandBundleDef> &Bundles) {
const FunctionProtoType *FP =
Callee.getAbstractInfo().getCalleeFunctionProtoType();
if (FP)
Bundles.emplace_back("kcfi", CGM.CreateKCFITypeId(FP->desugar()));
const CGCalleeInfo &CI = Callee.getAbstractInfo();
const FunctionProtoType *FP = CI.getCalleeFunctionProtoType();
if (!FP)
return;

StringRef Salt;
if (const auto &Info = FP->getExtraAttributeInfo())
Salt = Info.CFISalt;

Bundles.emplace_back("kcfi", CGM.CreateKCFITypeId(FP->desugar(), Salt));
}

llvm::Value *
Expand Down
Loading