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 10 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 @@ -4631,6 +4631,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 @@ -4639,14 +4642,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 KCFISalt;

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

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

/// 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 @@ -4676,11 +4691,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 @@ -4689,6 +4704,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(uint16_t)
unsigned AArch64SMEAttributes : 9;

FunctionTypeArmAttributes() : AArch64SMEAttributes(SME_NormalFunction) {}
Expand Down Expand Up @@ -5170,6 +5186,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 @@ -5259,19 +5276,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(uint16_t)
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 @@ -5296,13 +5316,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 @@ -5328,6 +5353,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 @@ -5420,6 +5450,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 @@ -5453,6 +5489,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 @@ -5640,6 +5677,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 @@ -3896,6 +3896,14 @@ def CFICanonicalJumpTable : InheritableAttr {
let SimpleHandler = 1;
}

def KCFISalt : DeclOrTypeAttr {
let Spellings = [Clang<"kcfi_salt">];
let Args = [StringArgument<"Salt">];
let Subjects = SubjectList<[Function, Field, Var, TypedefName], ErrorDiag>;
let Documentation = [KCFISaltDocs];
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
58 changes: 58 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -3643,6 +3643,64 @@ make the function's CFI jump table canonical. See :ref:`the CFI documentation
}];
}

def KCFISaltDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Use ``__attribute__((kcfi_salt("<salt>")))`` on a function declaration, function
definition, or typedef to help distinguish CFI hashes between functions with
the same type signature.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like in the kernel patch, it would be nice to summarize the use case, i.e. in what cases we would want to apply this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added some of Sami's examples to the docs. Let me know what you think.


Example use:

.. code-block:: c

// .h file:
#define __kcfi_salt __attribute__((kcfi_salt("vogon")))

// Convenient typedefs to avoid nested declarator syntax.
typedef int (*fptr_t)(void); // Non-salted function call.
typedef int (*fptr_salted_t)(void) __kcfi_salt;

struct widget_generator {
fptr_t init;
fptr_salted_t exec;
fptr_t teardown;
};

// 1st .c file:
static int internal_init(void) { /* ... */ }
static int internal_salted_exec(void) __kcfi_salt { /* ... */ }
static int internal_teardown(void) { /* ... */ }

static struct widget_generator _generator = {
.init = internal_init,
.exec = internal_salted_exec,
.teardown = internal_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();
}

}];
}

def DocCatTypeSafety : DocumentationCategory<"Type Safety Checking"> {
let Content = [{
Clang supports additional attributes to enable checking type safety properties
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5123,10 +5123,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
14 changes: 13 additions & 1 deletion clang/lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3677,6 +3677,16 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef<QualType> params,
FunctionTypeBits.HasExtraBitfields = false;
}

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

// 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 @@ -3894,7 +3904,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 @@ -3929,6 +3940,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
4 changes: 4 additions & 0 deletions clang/lib/AST/TypePrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2106,6 +2106,10 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
case attr::ExtVectorType:
OS << "ext_vector_type";
break;
case attr::KCFISalt:
OS << "kcfi_salt(\"" << cast<KCFISaltAttr>(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 @@ -2905,10 +2905,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.KCFISalt;

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

llvm::Value *
Expand Down
15 changes: 12 additions & 3 deletions clang/lib/CodeGen/CodeGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2338,7 +2338,7 @@ llvm::ConstantInt *CodeGenModule::CreateCrossDsoCfiTypeId(llvm::Metadata *MD) {
return llvm::ConstantInt::get(Int64Ty, llvm::MD5Hash(MDS->getString()));
}

llvm::ConstantInt *CodeGenModule::CreateKCFITypeId(QualType T) {
llvm::ConstantInt *CodeGenModule::CreateKCFITypeId(QualType T, StringRef Salt) {
if (auto *FnType = T->getAs<FunctionProtoType>())
T = getContext().getFunctionType(
FnType->getReturnType(), FnType->getParamTypes(),
Expand All @@ -2349,6 +2349,9 @@ llvm::ConstantInt *CodeGenModule::CreateKCFITypeId(QualType T) {
getCXXABI().getMangleContext().mangleCanonicalTypeName(
T, Out, getCodeGenOpts().SanitizeCfiICallNormalizeIntegers);

if (!Salt.empty())
Out << "." << Salt;

if (getCodeGenOpts().SanitizeCfiICallNormalizeIntegers)
Out << ".normalized";

Expand Down Expand Up @@ -3015,9 +3018,15 @@ void CodeGenModule::createFunctionTypeMetadataForIcall(const FunctionDecl *FD,
void CodeGenModule::setKCFIType(const FunctionDecl *FD, llvm::Function *F) {
llvm::LLVMContext &Ctx = F->getContext();
llvm::MDBuilder MDB(Ctx);
llvm::StringRef Salt;

if (const auto *FP = FD->getType()->getAs<FunctionProtoType>())
if (const auto &Info = FP->getExtraAttributeInfo())
Salt = Info.KCFISalt;

F->setMetadata(llvm::LLVMContext::MD_kcfi_type,
llvm::MDNode::get(
Ctx, MDB.createConstant(CreateKCFITypeId(FD->getType()))));
llvm::MDNode::get(Ctx, MDB.createConstant(CreateKCFITypeId(
FD->getType(), Salt))));
}

static bool allowKCFIIdentifier(StringRef Name) {
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/CodeGen/CodeGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -1621,7 +1621,7 @@ class CodeGenModule : public CodeGenTypeCache {
llvm::ConstantInt *CreateCrossDsoCfiTypeId(llvm::Metadata *MD);

/// Generate a KCFI type identifier for T.
llvm::ConstantInt *CreateKCFITypeId(QualType T);
llvm::ConstantInt *CreateKCFITypeId(QualType T, StringRef Salt);

/// Create a metadata identifier for the given type. This may either be an
/// MDString (for external identifiers) or a distinct unnamed MDNode (for
Expand Down
Loading