Skip to content

Commit ae18b94

Browse files
ahatanakrjmccall
andauthored
[PAC] Implement function pointer type discrimination (#96992)
Give users an option (-fptrauth-function-pointer-type-discrimination) to sign a function pointer using a non-zero discriminator based on the function type. The discriminator is computed by first translating the function type to a string and then computing the hash value of the string. Two function types that are compatible in C must be translated to the same string with the exception of function types that use typedefs of anonymous structs in their return type or parameter types. This patch doesn't have the code to resign function pointers, which is needed when a function pointer is converted to a different function type. That will be implemented in another patch. Co-authored-by: John McCall <[email protected]> --------- Co-authored-by: John McCall <[email protected]>
1 parent 1cbddce commit ae18b94

File tree

15 files changed

+565
-25
lines changed

15 files changed

+565
-25
lines changed

clang/include/clang/AST/ASTContext.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
12831283
uint16_t
12841284
getPointerAuthVTablePointerDiscriminator(const CXXRecordDecl *RD);
12851285

1286+
/// Return the "other" type-specific discriminator for the given type.
1287+
uint16_t getPointerAuthTypeDiscriminator(QualType T) const;
1288+
12861289
/// Apply Objective-C protocol qualifiers to the given type.
12871290
/// \param allowOnPointerType specifies if we can apply protocol
12881291
/// qualifiers on ObjCObjectPointerType. It can be set to true when

clang/include/clang/AST/Type.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2509,6 +2509,7 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase {
25092509
bool isFunctionNoProtoType() const { return getAs<FunctionNoProtoType>(); }
25102510
bool isFunctionProtoType() const { return getAs<FunctionProtoType>(); }
25112511
bool isPointerType() const;
2512+
bool isSignableType() const;
25122513
bool isAnyPointerType() const; // Any C pointer or ObjC object pointer
25132514
bool isCountAttributedType() const;
25142515
bool isBlockPointerType() const;
@@ -8004,6 +8005,8 @@ inline bool Type::isAnyPointerType() const {
80048005
return isPointerType() || isObjCObjectPointerType();
80058006
}
80068007

8008+
inline bool Type::isSignableType() const { return isPointerType(); }
8009+
80078010
inline bool Type::isBlockPointerType() const {
80088011
return isa<BlockPointerType>(CanonicalType);
80098012
}

clang/include/clang/Basic/Features.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ FEATURE(ptrauth_vtable_pointer_address_discrimination, LangOpts.PointerAuthVTPtr
110110
FEATURE(ptrauth_vtable_pointer_type_discrimination, LangOpts.PointerAuthVTPtrTypeDiscrimination)
111111
FEATURE(ptrauth_member_function_pointer_type_discrimination, LangOpts.PointerAuthCalls)
112112
FEATURE(ptrauth_init_fini, LangOpts.PointerAuthInitFini)
113+
FEATURE(ptrauth_function_pointer_type_discrimination, LangOpts.PointerAuthFunctionTypeDiscrimination)
113114
EXTENSION(swiftcc,
114115
PP.getTargetInfo().checkCallingConvention(CC_Swift) ==
115116
clang::TargetInfo::CCCR_OK)

clang/include/clang/Basic/LangOptions.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ LANGOPT(PointerAuthAuthTraps, 1, 0, "pointer authentication failure traps")
169169
LANGOPT(PointerAuthVTPtrAddressDiscrimination, 1, 0, "incorporate address discrimination in authenticated vtable pointers")
170170
LANGOPT(PointerAuthVTPtrTypeDiscrimination, 1, 0, "incorporate type discrimination in authenticated vtable pointers")
171171
LANGOPT(PointerAuthInitFini, 1, 0, "sign function pointers in init/fini arrays")
172+
BENIGN_LANGOPT(PointerAuthFunctionTypeDiscrimination, 1, 0,
173+
"Use type discrimination when signing function pointers")
172174

173175
LANGOPT(DoubleSquareBracketAttributes, 1, 0, "'[[]]' attributes extension for all language standard modes")
174176
LANGOPT(ExperimentalLateParseAttributes, 1, 0, "experimental late parsing of attributes")

clang/include/clang/CodeGen/CodeGenABITypes.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ unsigned getLLVMFieldNumber(CodeGenModule &CGM,
108108
/// Return a declaration discriminator for the given global decl.
109109
uint16_t getPointerAuthDeclDiscriminator(CodeGenModule &CGM, GlobalDecl GD);
110110

111+
/// Return a type discriminator for the given function type.
112+
uint16_t getPointerAuthTypeDiscriminator(CodeGenModule &CGM,
113+
QualType FunctionType);
114+
111115
/// Given the language and code-generation options that Clang was configured
112116
/// with, set the default LLVM IR attributes for a function definition.
113117
/// The attributes set here are mostly global target-configuration and

clang/include/clang/Driver/Options.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4234,6 +4234,8 @@ defm ptrauth_vtable_pointer_address_discrimination :
42344234
defm ptrauth_vtable_pointer_type_discrimination :
42354235
OptInCC1FFlag<"ptrauth-vtable-pointer-type-discrimination", "Enable type discrimination of vtable pointers">;
42364236
defm ptrauth_init_fini : OptInCC1FFlag<"ptrauth-init-fini", "Enable signing of function pointers in init/fini arrays">;
4237+
defm ptrauth_function_pointer_type_discrimination : OptInCC1FFlag<"ptrauth-function-pointer-type-discrimination",
4238+
"Enable type discrimination on C function pointers">;
42374239
}
42384240

42394241
def fenable_matrix : Flag<["-"], "fenable-matrix">, Group<f_Group>,

clang/lib/AST/ASTContext.cpp

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3140,6 +3140,285 @@ ASTContext::getPointerAuthVTablePointerDiscriminator(const CXXRecordDecl *RD) {
31403140
return llvm::getPointerAuthStableSipHash(Str);
31413141
}
31423142

3143+
/// Encode a function type for use in the discriminator of a function pointer
3144+
/// type. We can't use the itanium scheme for this since C has quite permissive
3145+
/// rules for type compatibility that we need to be compatible with.
3146+
///
3147+
/// Formally, this function associates every function pointer type T with an
3148+
/// encoded string E(T). Let the equivalence relation T1 ~ T2 be defined as
3149+
/// E(T1) == E(T2). E(T) is part of the ABI of values of type T. C type
3150+
/// compatibility requires equivalent treatment under the ABI, so
3151+
/// CCompatible(T1, T2) must imply E(T1) == E(T2), that is, CCompatible must be
3152+
/// a subset of ~. Crucially, however, it must be a proper subset because
3153+
/// CCompatible is not an equivalence relation: for example, int[] is compatible
3154+
/// with both int[1] and int[2], but the latter are not compatible with each
3155+
/// other. Therefore this encoding function must be careful to only distinguish
3156+
/// types if there is no third type with which they are both required to be
3157+
/// compatible.
3158+
static void encodeTypeForFunctionPointerAuth(const ASTContext &Ctx,
3159+
raw_ostream &OS, QualType QT) {
3160+
// FIXME: Consider address space qualifiers.
3161+
const Type *T = QT.getCanonicalType().getTypePtr();
3162+
3163+
// FIXME: Consider using the C++ type mangling when we encounter a construct
3164+
// that is incompatible with C.
3165+
3166+
switch (T->getTypeClass()) {
3167+
case Type::Atomic:
3168+
return encodeTypeForFunctionPointerAuth(
3169+
Ctx, OS, cast<AtomicType>(T)->getValueType());
3170+
3171+
case Type::LValueReference:
3172+
OS << "R";
3173+
encodeTypeForFunctionPointerAuth(Ctx, OS,
3174+
cast<ReferenceType>(T)->getPointeeType());
3175+
return;
3176+
case Type::RValueReference:
3177+
OS << "O";
3178+
encodeTypeForFunctionPointerAuth(Ctx, OS,
3179+
cast<ReferenceType>(T)->getPointeeType());
3180+
return;
3181+
3182+
case Type::Pointer:
3183+
// C11 6.7.6.1p2:
3184+
// For two pointer types to be compatible, both shall be identically
3185+
// qualified and both shall be pointers to compatible types.
3186+
// FIXME: we should also consider pointee types.
3187+
OS << "P";
3188+
return;
3189+
3190+
case Type::ObjCObjectPointer:
3191+
case Type::BlockPointer:
3192+
OS << "P";
3193+
return;
3194+
3195+
case Type::Complex:
3196+
OS << "C";
3197+
return encodeTypeForFunctionPointerAuth(
3198+
Ctx, OS, cast<ComplexType>(T)->getElementType());
3199+
3200+
case Type::VariableArray:
3201+
case Type::ConstantArray:
3202+
case Type::IncompleteArray:
3203+
case Type::ArrayParameter:
3204+
// C11 6.7.6.2p6:
3205+
// For two array types to be compatible, both shall have compatible
3206+
// element types, and if both size specifiers are present, and are integer
3207+
// constant expressions, then both size specifiers shall have the same
3208+
// constant value [...]
3209+
//
3210+
// So since ElemType[N] has to be compatible ElemType[], we can't encode the
3211+
// width of the array.
3212+
OS << "A";
3213+
return encodeTypeForFunctionPointerAuth(
3214+
Ctx, OS, cast<ArrayType>(T)->getElementType());
3215+
3216+
case Type::ObjCInterface:
3217+
case Type::ObjCObject:
3218+
OS << "<objc_object>";
3219+
return;
3220+
3221+
case Type::Enum:
3222+
// C11 6.7.2.2p4:
3223+
// Each enumerated type shall be compatible with char, a signed integer
3224+
// type, or an unsigned integer type.
3225+
//
3226+
// So we have to treat enum types as integers.
3227+
return encodeTypeForFunctionPointerAuth(
3228+
Ctx, OS, cast<EnumType>(T)->getDecl()->getIntegerType());
3229+
3230+
case Type::FunctionNoProto:
3231+
case Type::FunctionProto: {
3232+
// C11 6.7.6.3p15:
3233+
// For two function types to be compatible, both shall specify compatible
3234+
// return types. Moreover, the parameter type lists, if both are present,
3235+
// shall agree in the number of parameters and in the use of the ellipsis
3236+
// terminator; corresponding parameters shall have compatible types.
3237+
//
3238+
// That paragraph goes on to describe how unprototyped functions are to be
3239+
// handled, which we ignore here. Unprototyped function pointers are hashed
3240+
// as though they were prototyped nullary functions since thats probably
3241+
// what the user meant. This behavior is non-conforming.
3242+
// FIXME: If we add a "custom discriminator" function type attribute we
3243+
// should encode functions as their discriminators.
3244+
OS << "F";
3245+
const auto *FuncType = cast<FunctionType>(T);
3246+
encodeTypeForFunctionPointerAuth(Ctx, OS, FuncType->getReturnType());
3247+
if (const auto *FPT = dyn_cast<FunctionProtoType>(FuncType)) {
3248+
for (QualType Param : FPT->param_types()) {
3249+
Param = Ctx.getSignatureParameterType(Param);
3250+
encodeTypeForFunctionPointerAuth(Ctx, OS, Param);
3251+
}
3252+
if (FPT->isVariadic())
3253+
OS << "z";
3254+
}
3255+
OS << "E";
3256+
return;
3257+
}
3258+
3259+
case Type::MemberPointer: {
3260+
OS << "M";
3261+
const auto *MPT = T->getAs<MemberPointerType>();
3262+
encodeTypeForFunctionPointerAuth(Ctx, OS, QualType(MPT->getClass(), 0));
3263+
encodeTypeForFunctionPointerAuth(Ctx, OS, MPT->getPointeeType());
3264+
return;
3265+
}
3266+
case Type::ExtVector:
3267+
case Type::Vector:
3268+
OS << "Dv" << Ctx.getTypeSizeInChars(T).getQuantity();
3269+
break;
3270+
3271+
// Don't bother discriminating based on these types.
3272+
case Type::Pipe:
3273+
case Type::BitInt:
3274+
case Type::ConstantMatrix:
3275+
OS << "?";
3276+
return;
3277+
3278+
case Type::Builtin: {
3279+
const auto *BTy = T->getAs<BuiltinType>();
3280+
switch (BTy->getKind()) {
3281+
#define SIGNED_TYPE(Id, SingletonId) \
3282+
case BuiltinType::Id: \
3283+
OS << "i"; \
3284+
return;
3285+
#define UNSIGNED_TYPE(Id, SingletonId) \
3286+
case BuiltinType::Id: \
3287+
OS << "i"; \
3288+
return;
3289+
#define PLACEHOLDER_TYPE(Id, SingletonId) case BuiltinType::Id:
3290+
#define BUILTIN_TYPE(Id, SingletonId)
3291+
#include "clang/AST/BuiltinTypes.def"
3292+
llvm_unreachable("placeholder types should not appear here.");
3293+
3294+
case BuiltinType::Half:
3295+
OS << "Dh";
3296+
return;
3297+
case BuiltinType::Float:
3298+
OS << "f";
3299+
return;
3300+
case BuiltinType::Double:
3301+
OS << "d";
3302+
return;
3303+
case BuiltinType::LongDouble:
3304+
OS << "e";
3305+
return;
3306+
case BuiltinType::Float16:
3307+
OS << "DF16_";
3308+
return;
3309+
case BuiltinType::Float128:
3310+
OS << "g";
3311+
return;
3312+
3313+
case BuiltinType::Void:
3314+
OS << "v";
3315+
return;
3316+
3317+
case BuiltinType::ObjCId:
3318+
case BuiltinType::ObjCClass:
3319+
case BuiltinType::ObjCSel:
3320+
case BuiltinType::NullPtr:
3321+
OS << "P";
3322+
return;
3323+
3324+
// Don't bother discriminating based on OpenCL types.
3325+
case BuiltinType::OCLSampler:
3326+
case BuiltinType::OCLEvent:
3327+
case BuiltinType::OCLClkEvent:
3328+
case BuiltinType::OCLQueue:
3329+
case BuiltinType::OCLReserveID:
3330+
case BuiltinType::BFloat16:
3331+
case BuiltinType::VectorQuad:
3332+
case BuiltinType::VectorPair:
3333+
OS << "?";
3334+
return;
3335+
3336+
// Don't bother discriminating based on these seldom-used types.
3337+
case BuiltinType::Ibm128:
3338+
return;
3339+
#define IMAGE_TYPE(ImgType, Id, SingletonId, Access, Suffix) \
3340+
case BuiltinType::Id: \
3341+
return;
3342+
#include "clang/Basic/OpenCLImageTypes.def"
3343+
#define EXT_OPAQUE_TYPE(ExtType, Id, Ext) \
3344+
case BuiltinType::Id: \
3345+
return;
3346+
#include "clang/Basic/OpenCLExtensionTypes.def"
3347+
#define SVE_TYPE(Name, Id, SingletonId) \
3348+
case BuiltinType::Id: \
3349+
return;
3350+
#include "clang/Basic/AArch64SVEACLETypes.def"
3351+
case BuiltinType::Dependent:
3352+
llvm_unreachable("should never get here");
3353+
case BuiltinType::AMDGPUBufferRsrc:
3354+
case BuiltinType::WasmExternRef:
3355+
#define RVV_TYPE(Name, Id, SingletonId) case BuiltinType::Id:
3356+
#include "clang/Basic/RISCVVTypes.def"
3357+
llvm_unreachable("not yet implemented");
3358+
}
3359+
}
3360+
case Type::Record: {
3361+
const RecordDecl *RD = T->getAs<RecordType>()->getDecl();
3362+
const IdentifierInfo *II = RD->getIdentifier();
3363+
3364+
// In C++, an immediate typedef of an anonymous struct or union
3365+
// is considered to name it for ODR purposes, but C's specification
3366+
// of type compatibility does not have a similar rule. Using the typedef
3367+
// name in function type discriminators anyway, as we do here,
3368+
// therefore technically violates the C standard: two function pointer
3369+
// types defined in terms of two typedef'd anonymous structs with
3370+
// different names are formally still compatible, but we are assigning
3371+
// them different discriminators and therefore incompatible ABIs.
3372+
//
3373+
// This is a relatively minor violation that significantly improves
3374+
// discrimination in some cases and has not caused problems in
3375+
// practice. Regardless, it is now part of the ABI in places where
3376+
// function type discrimination is used, and it can no longer be
3377+
// changed except on new platforms.
3378+
3379+
if (!II)
3380+
if (const TypedefNameDecl *Typedef = RD->getTypedefNameForAnonDecl())
3381+
II = Typedef->getDeclName().getAsIdentifierInfo();
3382+
3383+
if (!II) {
3384+
OS << "<anonymous_record>";
3385+
return;
3386+
}
3387+
OS << II->getLength() << II->getName();
3388+
return;
3389+
}
3390+
case Type::DeducedTemplateSpecialization:
3391+
case Type::Auto:
3392+
#define NON_CANONICAL_TYPE(Class, Base) case Type::Class:
3393+
#define DEPENDENT_TYPE(Class, Base) case Type::Class:
3394+
#define NON_CANONICAL_UNLESS_DEPENDENT_TYPE(Class, Base) case Type::Class:
3395+
#define ABSTRACT_TYPE(Class, Base)
3396+
#define TYPE(Class, Base)
3397+
#include "clang/AST/TypeNodes.inc"
3398+
llvm_unreachable("unexpected non-canonical or dependent type!");
3399+
return;
3400+
}
3401+
}
3402+
3403+
uint16_t ASTContext::getPointerAuthTypeDiscriminator(QualType T) const {
3404+
assert(!T->isDependentType() &&
3405+
"cannot compute type discriminator of a dependent type");
3406+
3407+
SmallString<256> Str;
3408+
llvm::raw_svector_ostream Out(Str);
3409+
3410+
if (T->isFunctionPointerType() || T->isFunctionReferenceType())
3411+
T = T->getPointeeType();
3412+
3413+
if (T->isFunctionType())
3414+
encodeTypeForFunctionPointerAuth(*this, Out, T);
3415+
else
3416+
llvm_unreachable(
3417+
"type discrimination of non-function type not implemented yet");
3418+
3419+
return llvm::getPointerAuthStableSipHash(Str);
3420+
}
3421+
31433422
QualType ASTContext::getObjCGCQualType(QualType T,
31443423
Qualifiers::GC GCAttr) const {
31453424
QualType CanT = getCanonicalType(T);

0 commit comments

Comments
 (0)