[clang] [clang] Implement function pointer type discrimination (PR #96992)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 28 08:04:49 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-codegen
@llvm/pr-subscribers-clang
Author: Akira Hatanaka (ahatanak)
<details>
<summary>Changes</summary>
Give users an option to sign a function pointer using a non-zero discrimiantor based on the type of the destination.
---
Patch is 32.79 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/96992.diff
14 Files Affected:
- (modified) clang/include/clang/AST/ASTContext.h (+3)
- (modified) clang/include/clang/AST/Type.h (+3)
- (modified) clang/include/clang/Basic/Features.def (+1)
- (modified) clang/include/clang/Basic/LangOptions.def (+3)
- (modified) clang/include/clang/CodeGen/CodeGenABITypes.h (+4)
- (modified) clang/include/clang/Driver/Options.td (+2)
- (modified) clang/lib/AST/ASTContext.cpp (+263)
- (modified) clang/lib/CodeGen/CGExprConstant.cpp (+5)
- (modified) clang/lib/CodeGen/CGPointerAuth.cpp (+64-4)
- (modified) clang/lib/CodeGen/CodeGenModule.h (+4)
- (modified) clang/lib/Driver/ToolChains/Clang.cpp (+3)
- (modified) clang/lib/Frontend/CompilerInvocation.cpp (+9-2)
- (added) clang/test/CodeGen/ptrauth-function-type-discriminator.c (+137)
- (modified) clang/test/Preprocessor/ptrauth_feature.c (+27-7)
``````````diff
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index a99f2dc6eb3f2..8ba0c943a9c86 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -1283,6 +1283,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
uint16_t
getPointerAuthVTablePointerDiscriminator(const CXXRecordDecl *RD);
+ /// Return the "other" type-specific discriminator for the given type.
+ uint16_t getPointerAuthTypeDiscriminator(QualType T);
+
/// Apply Objective-C protocol qualifiers to the given type.
/// \param allowOnPointerType specifies if we can apply protocol
/// qualifiers on ObjCObjectPointerType. It can be set to true when
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index a98899f7f4222..3aa0f05b0ab60 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -2506,6 +2506,7 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
bool isFunctionNoProtoType() const { return getAs<FunctionNoProtoType>(); }
bool isFunctionProtoType() const { return getAs<FunctionProtoType>(); }
bool isPointerType() const;
+ bool isSignableType() const;
bool isAnyPointerType() const; // Any C pointer or ObjC object pointer
bool isCountAttributedType() const;
bool isBlockPointerType() const;
@@ -8001,6 +8002,8 @@ inline bool Type::isAnyPointerType() const {
return isPointerType() || isObjCObjectPointerType();
}
+inline bool Type::isSignableType() const { return isPointerType(); }
+
inline bool Type::isBlockPointerType() const {
return isa<BlockPointerType>(CanonicalType);
}
diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def
index 53f410d3cb4bd..2f864ff1c0edf 100644
--- a/clang/include/clang/Basic/Features.def
+++ b/clang/include/clang/Basic/Features.def
@@ -110,6 +110,7 @@ FEATURE(ptrauth_vtable_pointer_address_discrimination, LangOpts.PointerAuthVTPtr
FEATURE(ptrauth_vtable_pointer_type_discrimination, LangOpts.PointerAuthVTPtrTypeDiscrimination)
FEATURE(ptrauth_member_function_pointer_type_discrimination, LangOpts.PointerAuthCalls)
FEATURE(ptrauth_init_fini, LangOpts.PointerAuthInitFini)
+FEATURE(ptrauth_function_pointer_type_discrimination, LangOpts.PointerAuthFunctionTypeDiscrimination)
EXTENSION(swiftcc,
PP.getTargetInfo().checkCallingConvention(CC_Swift) ==
clang::TargetInfo::CCCR_OK)
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 6dd6b5614f44c..7521ab85a9b70 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -470,6 +470,9 @@ ENUM_LANGOPT(StrictFlexArraysLevel, StrictFlexArraysLevelKind, 2,
COMPATIBLE_VALUE_LANGOPT(MaxTokens, 32, 0, "Max number of tokens per TU or 0")
+BENIGN_LANGOPT(PointerAuthFunctionTypeDiscrimination, 1, 0,
+ "Use type discrimination when signing function pointers")
+
ENUM_LANGOPT(SignReturnAddressScope, SignReturnAddressScopeKind, 2, SignReturnAddressScopeKind::None,
"Scope of return address signing")
ENUM_LANGOPT(SignReturnAddressKey, SignReturnAddressKeyKind, 1, SignReturnAddressKeyKind::AKey,
diff --git a/clang/include/clang/CodeGen/CodeGenABITypes.h b/clang/include/clang/CodeGen/CodeGenABITypes.h
index d4822dc160082..9cbc5a8a2a3f4 100644
--- a/clang/include/clang/CodeGen/CodeGenABITypes.h
+++ b/clang/include/clang/CodeGen/CodeGenABITypes.h
@@ -108,6 +108,10 @@ unsigned getLLVMFieldNumber(CodeGenModule &CGM,
/// Return a declaration discriminator for the given global decl.
uint16_t getPointerAuthDeclDiscriminator(CodeGenModule &CGM, GlobalDecl GD);
+/// Return a type discriminator for the given function type.
+uint16_t getPointerAuthTypeDiscriminator(CodeGenModule &CGM,
+ QualType FunctionType);
+
/// Given the language and code-generation options that Clang was configured
/// with, set the default LLVM IR attributes for a function definition.
/// The attributes set here are mostly global target-configuration and
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index dd55838dcf384..a6f960e42d23f 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -4228,6 +4228,8 @@ defm ptrauth_vtable_pointer_address_discrimination :
defm ptrauth_vtable_pointer_type_discrimination :
OptInCC1FFlag<"ptrauth-vtable-pointer-type-discrimination", "Enable type discrimination of vtable pointers">;
defm ptrauth_init_fini : OptInCC1FFlag<"ptrauth-init-fini", "Enable signing of function pointers in init/fini arrays">;
+defm ptrauth_function_pointer_type_discrimination : OptInCC1FFlag<"ptrauth-function-pointer-type-discrimination",
+ "Enabling type discrimination on C function pointers">;
}
def fenable_matrix : Flag<["-"], "fenable-matrix">, Group<f_Group>,
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 84deaf5429df7..74e0ae0a58e9f 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -3140,6 +3140,269 @@ ASTContext::getPointerAuthVTablePointerDiscriminator(const CXXRecordDecl *RD) {
return llvm::getPointerAuthStableSipHash(Str);
}
+/// Encode a function type for use in the discriminator of a function pointer
+/// type. We can't use the itanium scheme for this since C has quite permissive
+/// rules for type compatibility that we need to be compatible with.
+///
+/// Formally, this function associates every function pointer type T with an
+/// encoded string E(T). Let the equivalence relation T1 ~ T2 be defined as
+/// E(T1) == E(T2). E(T) is part of the ABI of values of type T. C type
+/// compatibility requires equivalent treatment under the ABI, so
+/// CCompatible(T1, T2) must imply E(T1) == E(T2), that is, CCompatible must be
+/// a subset of ~. Crucially, however, it must be a proper subset because
+/// CCompatible is not an equivalence relation: for example, int[] is compatible
+/// with both int[1] and int[2], but the latter are not compatible with each
+/// other. Therefore this encoding function must be careful to only distinguish
+/// types if there is no third type with which they are both required to be
+/// compatible.
+static void encodeTypeForFunctionPointerAuth(ASTContext &Ctx, raw_ostream &OS,
+ QualType QT) {
+ // FIXME: Consider address space qualifiers.
+ const Type *T = QT.getCanonicalType().getTypePtr();
+
+ // FIXME: Consider using the C++ type mangling when we encounter a construct
+ // that is incompatible with C.
+
+ switch (T->getTypeClass()) {
+ case Type::Atomic:
+ return encodeTypeForFunctionPointerAuth(
+ Ctx, OS, cast<AtomicType>(T)->getValueType());
+
+ case Type::LValueReference:
+ OS << "R";
+ encodeTypeForFunctionPointerAuth(Ctx, OS,
+ cast<ReferenceType>(T)->getPointeeType());
+ return;
+ case Type::RValueReference:
+ OS << "O";
+ encodeTypeForFunctionPointerAuth(Ctx, OS,
+ cast<ReferenceType>(T)->getPointeeType());
+ return;
+
+ case Type::Pointer:
+ // C11 6.7.6.1p2:
+ // For two pointer types to be compatible, both shall be identically
+ // qualified and both shall be pointers to compatible types.
+ // FIXME: we should also consider pointee types.
+ OS << "P";
+ return;
+
+ case Type::ObjCObjectPointer:
+ case Type::BlockPointer:
+ OS << "P";
+ return;
+
+ case Type::Complex:
+ OS << "C";
+ return encodeTypeForFunctionPointerAuth(
+ Ctx, OS, cast<ComplexType>(T)->getElementType());
+
+ case Type::VariableArray:
+ case Type::ConstantArray:
+ case Type::IncompleteArray:
+ case Type::ArrayParameter:
+ // C11 6.7.6.2p6:
+ // For two array types to be compatible, both shall have compatible
+ // element types, and if both size specifiers are present, and are integer
+ // constant expressions, then both size specifiers shall have the same
+ // constant value [...]
+ //
+ // So since ElemType[N] has to be compatible ElemType[], we can't encode the
+ // width of the array.
+ OS << "A";
+ return encodeTypeForFunctionPointerAuth(
+ Ctx, OS, cast<ArrayType>(T)->getElementType());
+
+ case Type::ObjCInterface:
+ case Type::ObjCObject:
+ OS << "<objc_object>";
+ return;
+
+ case Type::Enum:
+ // C11 6.7.2.2p4:
+ // Each enumerated type shall be compatible with char, a signed integer
+ // type, or an unsigned integer type.
+ //
+ // So we have to treat enum types as integers.
+ OS << "i";
+ return;
+
+ case Type::FunctionNoProto:
+ case Type::FunctionProto: {
+ // C11 6.7.6.3p15:
+ // For two function types to be compatible, both shall specify compatible
+ // return types. Moreover, the parameter type lists, if both are present,
+ // shall agree in the number of parameters and in the use of the ellipsis
+ // terminator; corresponding parameters shall have compatible types.
+ //
+ // That paragraph goes on to describe how unprototyped functions are to be
+ // handled, which we ignore here. Unprototyped function pointers are hashed
+ // as though they were prototyped nullary functions since thats probably
+ // what the user meant. This behavior is non-conforming.
+ // FIXME: If we add a "custom discriminator" function type attribute we
+ // should encode functions as their discriminators.
+ OS << "F";
+ auto *FuncType = cast<FunctionType>(T);
+ encodeTypeForFunctionPointerAuth(Ctx, OS, FuncType->getReturnType());
+ if (auto *FPT = dyn_cast<FunctionProtoType>(FuncType)) {
+ for (QualType Param : FPT->param_types()) {
+ Param = Ctx.getSignatureParameterType(Param);
+ encodeTypeForFunctionPointerAuth(Ctx, OS, Param);
+ }
+ if (FPT->isVariadic())
+ OS << "z";
+ }
+ OS << "E";
+ return;
+ }
+
+ case Type::MemberPointer: {
+ OS << "M";
+ auto *MPT = T->getAs<MemberPointerType>();
+ encodeTypeForFunctionPointerAuth(Ctx, OS, QualType(MPT->getClass(), 0));
+ encodeTypeForFunctionPointerAuth(Ctx, OS, MPT->getPointeeType());
+ return;
+ }
+ case Type::ExtVector:
+ case Type::Vector:
+ OS << "Dv" << Ctx.getTypeSizeInChars(T).getQuantity();
+ break;
+
+ // Don't bother discriminating based on these types.
+ case Type::Pipe:
+ case Type::BitInt:
+ case Type::ConstantMatrix:
+ OS << "?";
+ return;
+
+ case Type::Builtin: {
+ const BuiltinType *BTy = T->getAs<BuiltinType>();
+ switch (BTy->getKind()) {
+#define SIGNED_TYPE(Id, SingletonId) \
+ case BuiltinType::Id: \
+ OS << "i"; \
+ return;
+#define UNSIGNED_TYPE(Id, SingletonId) \
+ case BuiltinType::Id: \
+ OS << "i"; \
+ return;
+#define PLACEHOLDER_TYPE(Id, SingletonId) case BuiltinType::Id:
+#define BUILTIN_TYPE(Id, SingletonId)
+#include "clang/AST/BuiltinTypes.def"
+ llvm_unreachable("placeholder types should not appear here.");
+
+ case BuiltinType::Half:
+ OS << "Dh";
+ return;
+ case BuiltinType::Float:
+ OS << "f";
+ return;
+ case BuiltinType::Double:
+ OS << "d";
+ return;
+ case BuiltinType::LongDouble:
+ OS << "e";
+ return;
+ case BuiltinType::Float16:
+ OS << "DF16_";
+ return;
+ case BuiltinType::Float128:
+ OS << "g";
+ return;
+
+ case BuiltinType::Void:
+ OS << "v";
+ return;
+
+ case BuiltinType::ObjCId:
+ case BuiltinType::ObjCClass:
+ case BuiltinType::ObjCSel:
+ case BuiltinType::NullPtr:
+ OS << "P";
+ return;
+
+ // Don't bother discriminating based on OpenCL types.
+ case BuiltinType::OCLSampler:
+ case BuiltinType::OCLEvent:
+ case BuiltinType::OCLClkEvent:
+ case BuiltinType::OCLQueue:
+ case BuiltinType::OCLReserveID:
+ case BuiltinType::BFloat16:
+ case BuiltinType::VectorQuad:
+ case BuiltinType::VectorPair:
+ OS << "?";
+ return;
+
+ // Don't bother discriminating based on these seldom-used types.
+ case BuiltinType::Ibm128:
+ return;
+#define IMAGE_TYPE(ImgType, Id, SingletonId, Access, Suffix) \
+ case BuiltinType::Id: \
+ return;
+#include "clang/Basic/OpenCLImageTypes.def"
+#define EXT_OPAQUE_TYPE(ExtType, Id, Ext) \
+ case BuiltinType::Id: \
+ return;
+#include "clang/Basic/OpenCLExtensionTypes.def"
+#define SVE_TYPE(Name, Id, SingletonId) \
+ case BuiltinType::Id: \
+ return;
+#include "clang/Basic/AArch64SVEACLETypes.def"
+ case BuiltinType::Dependent:
+ llvm_unreachable("should never get here");
+ case BuiltinType::AMDGPUBufferRsrc:
+ case BuiltinType::WasmExternRef:
+#define RVV_TYPE(Name, Id, SingletonId) case BuiltinType::Id:
+#include "clang/Basic/RISCVVTypes.def"
+ llvm_unreachable("not yet implemented");
+ }
+ }
+ case Type::Record: {
+ RecordDecl *RD = T->getAs<RecordType>()->getDecl();
+ IdentifierInfo *II = RD->getIdentifier();
+ if (!II)
+ if (const TypedefNameDecl *Typedef = RD->getTypedefNameForAnonDecl())
+ II = Typedef->getDeclName().getAsIdentifierInfo();
+
+ if (!II) {
+ OS << "<anonymous_record>";
+ return;
+ }
+ OS << II->getLength() << II->getName();
+ return;
+ }
+ case Type::DeducedTemplateSpecialization:
+ case Type::Auto:
+#define NON_CANONICAL_TYPE(Class, Base) case Type::Class:
+#define DEPENDENT_TYPE(Class, Base) case Type::Class:
+#define NON_CANONICAL_UNLESS_DEPENDENT_TYPE(Class, Base) case Type::Class:
+#define ABSTRACT_TYPE(Class, Base)
+#define TYPE(Class, Base)
+#include "clang/AST/TypeNodes.inc"
+ llvm_unreachable("unexpected non-canonical or dependent type!");
+ return;
+ }
+}
+
+uint16_t ASTContext::getPointerAuthTypeDiscriminator(QualType T) {
+ assert(!T->isDependentType() &&
+ "cannot compute type discriminator of a dependent type");
+
+ SmallString<256> Str;
+ llvm::raw_svector_ostream Out(Str);
+
+ if (T->isFunctionPointerType() || T->isFunctionReferenceType())
+ T = T->getPointeeType();
+
+ if (T->isFunctionType())
+ encodeTypeForFunctionPointerAuth(*this, Out, T);
+ else
+ llvm_unreachable(
+ "type discrimination of non-function type not implemented yet");
+
+ return llvm::getPointerAuthStableSipHash(Str);
+}
+
QualType ASTContext::getObjCGCQualType(QualType T,
Qualifiers::GC GCAttr) const {
QualType CanT = getCanonicalType(T);
diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp
index 1fec587b5c4c7..6d412af5cc994 100644
--- a/clang/lib/CodeGen/CGExprConstant.cpp
+++ b/clang/lib/CodeGen/CGExprConstant.cpp
@@ -2220,6 +2220,11 @@ llvm::Constant *ConstantLValueEmitter::emitPointerAuthPointer(const Expr *E) {
// The assertions here are all checked by Sema.
assert(Result.Val.isLValue());
+ auto *Base = Result.Val.getLValueBase().get<const ValueDecl *>();
+ if (auto *Decl = dyn_cast_or_null<FunctionDecl>(Base)) {
+ assert(Result.Val.getLValueOffset().isZero());
+ return CGM.getRawFunctionPointer(Decl);
+ }
return ConstantEmitter(CGM, Emitter.CGF)
.emitAbstract(E->getExprLoc(), Result.Val, E->getType());
}
diff --git a/clang/lib/CodeGen/CGPointerAuth.cpp b/clang/lib/CodeGen/CGPointerAuth.cpp
index 673f6e60bfc31..33b421437c74c 100644
--- a/clang/lib/CodeGen/CGPointerAuth.cpp
+++ b/clang/lib/CodeGen/CGPointerAuth.cpp
@@ -15,6 +15,7 @@
#include "CodeGenModule.h"
#include "clang/CodeGen/CodeGenABITypes.h"
#include "clang/CodeGen/ConstantInitBuilder.h"
+#include "llvm/Analysis/ValueTracking.h"
#include "llvm/Support/SipHash.h"
using namespace clang;
@@ -29,7 +30,9 @@ llvm::ConstantInt *CodeGenModule::getPointerAuthOtherDiscriminator(
return nullptr;
case PointerAuthSchema::Discrimination::Type:
- llvm_unreachable("type discrimination not implemented yet");
+ assert(!Type.isNull() && "type not provided for type-discriminated schema");
+ return llvm::ConstantInt::get(
+ IntPtrTy, getContext().getPointerAuthTypeDiscriminator(Type));
case PointerAuthSchema::Discrimination::Decl:
assert(Decl.getDecl() &&
@@ -43,6 +46,11 @@ llvm::ConstantInt *CodeGenModule::getPointerAuthOtherDiscriminator(
llvm_unreachable("bad discrimination kind");
}
+uint16_t CodeGen::getPointerAuthTypeDiscriminator(CodeGenModule &CGM,
+ QualType FunctionType) {
+ return CGM.getContext().getPointerAuthTypeDiscriminator(FunctionType);
+}
+
uint16_t CodeGen::getPointerAuthDeclDiscriminator(CodeGenModule &CGM,
GlobalDecl Declaration) {
return CGM.getPointerAuthDeclDiscriminator(Declaration);
@@ -71,12 +79,15 @@ CGPointerAuthInfo CodeGenModule::getFunctionPointerAuthInfo(QualType T) {
assert(!Schema.isAddressDiscriminated() &&
"function pointers cannot use address-specific discrimination");
- assert(!Schema.hasOtherDiscrimination() &&
- "function pointers don't support any discrimination yet");
+ llvm::Constant *Discriminator = nullptr;
+ if (T->isFunctionPointerType() || T->isFunctionReferenceType())
+ T = T->getPointeeType();
+ if (T->isFunctionType())
+ Discriminator = getPointerAuthOtherDiscriminator(Schema, GlobalDecl(), T);
return CGPointerAuthInfo(Schema.getKey(), Schema.getAuthenticationMode(),
/*IsaPointer=*/false, /*AuthenticatesNull=*/false,
- /*Discriminator=*/nullptr);
+ Discriminator);
}
llvm::Value *
@@ -114,6 +125,47 @@ CGPointerAuthInfo CodeGenFunction::EmitPointerAuthInfo(
Schema.authenticatesNullValues(), Discriminator);
}
+/// Return the natural pointer authentication for values of the given
+/// pointee type.
+static CGPointerAuthInfo
+getPointerAuthInfoForPointeeType(CodeGenModule &CGM, QualType PointeeType) {
+ if (PointeeType.isNull())
+ return CGPointerAuthInfo();
+
+ // Function pointers use the function-pointer schema by default.
+ if (PointeeType->isFunctionType())
+ return CGM.getFunctionPointerAuthInfo(PointeeType);
+
+ // Normal data pointers never use direct pointer authentication by default.
+ return CGPointerAuthInfo();
+}
+
+CGPointerAuthInfo CodeGenModule::getPointerAuthInfoForPointeeType(QualType T) {
+ return ::getPointerAuthInfoForPointeeType(*this, T);
+}
+
+/// Return the natural pointer authentication for values of the given
+/// pointer type.
+static CGPointerAuthInfo getPointerAuthInfoForType(CodeGenModule &CGM,
+ QualType PointerType) {
+ assert(PointerType->isSignableType());
+
+ // Block pointers are currently not signed.
+ if (PointerType->isBlockPointerType())
+ return CGPointerAuthInfo();
+
+ auto PointeeType = PointerType->getPointeeType();
+
+ if (PointeeType.isNull())
+ return CGPointerAuthInfo();
+
+ return ::getPointerAuthInfoForPointeeType(CGM, PointeeType);
+}
+
+CGPointerAuthInfo CodeGenModule::getPointerAuthInfoForType(QualType T) {
+ return ::getPointerAuthInfoForType(*this, T);
+}
+
llvm::Constant *
CodeGenModule::getConstantSignedPointer(llvm::Constant *Pointer, unsigned Key,
llvm::Constant *StorageAddress,
@@ -180,6 +232,14 @@ llvm::Constant *CodeGenModule::getFunctionPointer(GlobalDecl GD,
llvm::Type *Ty) {
const auto *FD = cast<FunctionDecl>(GD.getDecl());
QualType FuncType = FD->getType();
+
+ // Annoyingly, K&R functions have prototypes in the clang AST, but
+ // expressions referring to them are unprototyped.
+ if (!FD->hasPrototype())
+ if (const auto *Proto = FuncType->getAs<FunctionProtoType>()...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/96992
More information about the cfe-commits
mailing list