Skip to content

[PAC] Add support for __ptrauth type qualifier #100830

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 22 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
51c21ae
[PAC] Add support for __ptrauth type qualifier
ahatanak Jul 26, 2024
d0021ea
Fix coding style violations
ahatanak Jul 27, 2024
66c1f79
Revert unnecessary change
ahatanak Jul 27, 2024
73a2422
Fix coding style violations
ahatanak Jul 30, 2024
f098322
Strip qualifiers when evaluating a reference as an rvalue
ahatanak Aug 1, 2024
a760923
Resign pointer when materializing temporary
ahatanak Aug 1, 2024
d64227d
Address review comments
ahatanak Aug 5, 2024
a8c8554
Address review comments
ahatanak Aug 5, 2024
ad7f94e
Run tests with triple aarch64-linux-gnu
ahatanak Aug 7, 2024
0868bf3
Merge remote-tracking branch 'origin/main' into ptrauth-qualifier
ahatanak Jan 10, 2025
c343b0e
Fix check strings
ahatanak Jan 13, 2025
8f2fa5a
Merge remote-tracking branch 'origin/main' into ptrauth-qualifier
ahatanak Jan 16, 2025
fe01536
Remove __ptrauth_restricted_intptr from docs and diagnostic message
ahatanak Jan 22, 2025
b6adace
Merge branch 'main' into ptrauth-qualifier
ahatanak Feb 10, 2025
c965ad0
Address review comments
ahatanak Feb 24, 2025
ae8a37e
Use enum_select
ahatanak Feb 27, 2025
c95c9dd
Address review comments
ahatanak Mar 13, 2025
636e815
Add tests for rejecting template parameters being passed to __ptrauth
ahatanak Mar 21, 2025
02299cb
Add test for _Generic, overloadable functions, and arrays
ahatanak Mar 31, 2025
24f33cd
Add tests for constexpr, lambda, and concept
ahatanak Apr 10, 2025
9cbf8b0
Add more tests for concept
ahatanak Apr 10, 2025
45e5b93
Implement mangling/demangling __ptrauth on Windows
ahatanak Apr 15, 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
46 changes: 46 additions & 0 deletions clang/docs/PointerAuthentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,52 @@ a number of different tests.
normal interface. This may be true even on targets where pointer
authentication is not enabled by default.

__ptrauth Qualifier
^^^^^^^^^^^^^^^^^^^

``__ptrauth(key, address, discriminator)`` is an extended type
qualifier which causes so-qualified objects to hold pointers signed using the
specified schema rather than the default schema for such types.

In the current implementation in Clang, the qualified type must be a C pointer
type, either to a function or to an object. It currently cannot be an
Objective-C pointer type, a C++ reference type, or a block pointer type; these
restrictions may be lifted in the future.

The qualifier's operands are as follows:

- ``key`` - an expression evaluating to a key value from ``<ptrauth.h>``; must
be a constant expression

- ``address`` - whether to use address diversity (1) or not (0); must be
a constant expression with one of these two values

- ``discriminator`` - a constant discriminator; must be a constant expression

See `Discriminators`_ for more information about discriminators.

Currently the operands must be constant-evaluable even within templates. In the
future this restriction may be lifted to allow value-dependent expressions as
long as they instantiate to a constant expression.

Consistent with the ordinary C/C++ rule for parameters, top-level ``__ptrauth``
qualifiers on a parameter (after parameter type adjustment) are ignored when
deriving the type of the function. The parameter will be passed using the
default ABI for the unqualified pointer type.

If ``x`` is an object of type ``__ptrauth(key, address, discriminator) T``,
then the signing schema of the value stored in ``x`` is a key of ``key`` and
a discriminator determined as follows:

- if ``address`` is 0, then the discriminator is ``discriminator``;

- if ``address`` is 1 and ``discriminator`` is 0, then the discriminator is
``&x``; otherwise

- if ``address`` is 1 and ``discriminator`` is non-zero, then the discriminator
is ``ptrauth_blend_discriminator(&x, discriminator)``; see
`ptrauth_blend_discriminator`_.

``<ptrauth.h>``
~~~~~~~~~~~~~~~

Expand Down
2 changes: 2 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ X86 Support
Arm and AArch64 Support
^^^^^^^^^^^^^^^^^^^^^^^

- Support for __ptrauth type qualifier has been added.

Android Support
^^^^^^^^^^^^^^^

Expand Down
21 changes: 17 additions & 4 deletions clang/include/clang/AST/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,12 @@ class PointerAuthQualifier {
return Result;
}

std::string getAsString() const;
std::string getAsString(const PrintingPolicy &Policy) const;

bool isEmptyWhenPrinted(const PrintingPolicy &Policy) const;
void print(raw_ostream &OS, const PrintingPolicy &Policy) const;

void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(Data); }
};

Expand Down Expand Up @@ -562,7 +568,7 @@ class Qualifiers {

bool hasAddressSpace() const { return Mask & AddressSpaceMask; }
LangAS getAddressSpace() const {
return static_cast<LangAS>(Mask >> AddressSpaceShift);
return static_cast<LangAS>((Mask & AddressSpaceMask) >> AddressSpaceShift);
}
bool hasTargetSpecificAddressSpace() const {
return isTargetAddressSpace(getAddressSpace());
Expand Down Expand Up @@ -803,17 +809,18 @@ class Qualifiers {
static_assert(sizeof(PointerAuthQualifier) == sizeof(uint32_t),
"PointerAuthQualifier must be 32 bits");

static constexpr uint64_t PtrAuthShift = 32;
static constexpr uint64_t PtrAuthMask = UINT64_C(0xffffffff) << PtrAuthShift;

static constexpr uint64_t UMask = 0x8;
static constexpr uint64_t UShift = 3;
static constexpr uint64_t GCAttrMask = 0x30;
static constexpr uint64_t GCAttrShift = 4;
static constexpr uint64_t LifetimeMask = 0x1C0;
static constexpr uint64_t LifetimeShift = 6;
static constexpr uint64_t AddressSpaceMask =
~(CVRMask | UMask | GCAttrMask | LifetimeMask);
~(CVRMask | UMask | GCAttrMask | LifetimeMask | PtrAuthMask);
static constexpr uint64_t AddressSpaceShift = 9;
static constexpr uint64_t PtrAuthShift = 32;
static constexpr uint64_t PtrAuthMask = uint64_t(0xffffffff) << PtrAuthShift;
};

class QualifiersAndAtomic {
Expand Down Expand Up @@ -1449,6 +1456,12 @@ class QualType {
return getQualifiers().getPointerAuth();
}

bool hasAddressDiscriminatedPointerAuth() const {
if (PointerAuthQualifier PtrAuth = getPointerAuth())
return PtrAuth.isAddressDiscriminated();
return false;
}

enum PrimitiveDefaultInitializeKind {
/// The type does not fall into any of the following categories. Note that
/// this case is zero-valued so that values of this enum can be used as a
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 @@ -3514,6 +3514,14 @@ def ObjCRequiresPropertyDefs : InheritableAttr {
let SimpleHandler = 1;
}

def PointerAuth : TypeAttr {
let Spellings = [CustomKeyword<"__ptrauth">];
let Args = [IntArgument<"Key">,
BoolArgument<"AddressDiscriminated", 1>,
IntArgument<"ExtraDiscriminator", 1>];
let Documentation = [PtrAuthDocs];
}

def Unused : InheritableAttr {
let Spellings = [CXX11<"", "maybe_unused", 201603>, GCC<"unused">,
C23<"", "maybe_unused", 202106>];
Expand Down
28 changes: 28 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -2134,6 +2134,34 @@ Also see the documentation for `@available
}];
}

def PtrAuthDocs : Documentation {
let Category = DocCatVariable;
let Heading = "__ptrauth";
let Content = [{
The ``__ptrauth`` qualifier allows the programmer to directly control
how pointers are signed when they are stored in a particular variable.
This can be used to strengthen the default protections of pointer
authentication and make it more difficult for an attacker to escalate
an ability to alter memory into full control of a process.

.. code-block:: c

#include <ptrauth.h>

typedef void (*my_callback)(const void*);
my_callback __ptrauth(ptrauth_key_process_dependent_code, 1, 0xe27a) callback;

The first argument to ``__ptrauth`` is the name of the signing key.
Valid key names for the target are defined in ``<ptrauth.h>``.

The second argument to ``__ptrauth`` is a flag (0 or 1) specifying whether
the object should use address discrimination.

The third argument to ``__ptrauth`` is a 16-bit non-negative integer which
allows additional discrimination between objects.
}];
}

def ExternalSourceSymbolDocs : Documentation {
let Category = DocCatDecl;
let Content = [{
Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -1710,6 +1710,9 @@ def warn_pragma_unroll_cuda_value_in_parens : Warning<
"argument to '#pragma unroll' should not be in parentheses in CUDA C/C++">,
InGroup<CudaCompat>;

def err_ptrauth_qualifier_bad_arg_count : Error<
"'__ptrauth' qualifier must take between 1 and 3 arguments">;

def warn_cuda_attr_lambda_position : Warning<
"nvcc does not allow '__%0__' to appear after the parameter list in lambdas">,
InGroup<CudaCompat>;
Expand Down
43 changes: 41 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,22 @@ def err_ptrauth_indirect_goto_addrlabel_arithmetic : Error<
"%select{subtraction|addition}0 of address-of-label expressions is not "
"supported with ptrauth indirect gotos">;

// __ptrauth qualifier
def err_ptrauth_qualifier_invalid : Error<
"%select{return type|parameter type|property}1 may not be qualified with '__ptrauth'; type is %0">;
def err_ptrauth_qualifier_cast : Error<
"cannot cast to '__ptrauth'-qualified type %0">;
def err_ptrauth_qualifier_nonpointer : Error<
"'__ptrauth' qualifier only applies to pointer types; %0 is invalid">;
def err_ptrauth_qualifier_redundant : Error<
"type %0 is already %1-qualified">;
def err_ptrauth_arg_not_ice : Error<
"argument to '__ptrauth' must be an integer constant expression">;
def err_ptrauth_address_discrimination_invalid : Error<
"invalid address discrimination flag '%0'; '__ptrauth' requires '0' or '1'">;
def err_ptrauth_extra_discriminator_invalid : Error<
"invalid extra discriminator flag '%0'; '__ptrauth' requires a value between '0' and '%1'">;

/// main()
// static main() is not an error in C, just in C++.
def warn_static_main : Warning<"'main' should not be declared static">,
Expand Down Expand Up @@ -3943,7 +3959,8 @@ def note_cannot_use_trivial_abi_reason : Note<
"its copy constructors and move constructors are all deleted|"
"it is polymorphic|"
"it has a base of a non-trivial class type|it has a virtual base|"
"it has a __weak field|it has a field of a non-trivial class type}1">;
"it has a __weak field|it has a field of a non-trivial class type|"
"it has an address-discriminated '__ptrauth' field}1">;

// Availability attribute
def warn_availability_unknown_platform : Warning<
Expand Down Expand Up @@ -5017,6 +5034,10 @@ def note_ovl_candidate_bad_ownership : Note<
"%select{no|__unsafe_unretained|__strong|__weak|__autoreleasing}4 ownership,"
" but parameter has %select{no|__unsafe_unretained|__strong|__weak|"
"__autoreleasing}5 ownership">;
def note_ovl_candidate_bad_ptrauth : Note<
"candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: "
"%ordinal8 argument (%3) has %select{no '__ptrauth'|%5}4 qualifier,"
" but parameter has %select{no '__ptrauth'|%7}6 qualifier">;
def note_ovl_candidate_bad_cvr_this : Note<
"candidate %sub{select_ovl_candidate_kind}0,1,2 not viable: "
"'this' argument has type %3, but method is not marked "
Expand Down Expand Up @@ -6112,7 +6133,7 @@ def note_deleted_special_member_class_subobject : Note<
"%select{default|corresponding|default|default|default}4 constructor}0|"
"destructor}5"
"%select{||s||}4"
"|is an ObjC pointer}6">;
"|is an ObjC pointer|has an address-discriminated '__ptrauth' qualifier}6">;
def note_default_constructed_field
: Note<"default constructed field %0 declared here">;
def note_deleted_default_ctor_uninit_field : Note<
Expand Down Expand Up @@ -8955,6 +8976,19 @@ def err_typecheck_incompatible_ownership : Error<
"sending to parameter of different type}0,1"
"|%diff{casting $ to type $|casting between types}0,1}2"
" changes retain/release properties of pointer">;
def err_typecheck_incompatible_ptrauth : Error<
"%enum_select<AssignmentAction>{%Assigning{%diff{assigning $ to $|assigning to different types}1,0}"
"|%Passing{%diff{passing $ to parameter of type $|"
"passing to parameter of different type}0,1}"
"|%Returning{%diff{returning $ from a function with result type $|"
"returning from function with different return type}0,1}"
"|%Converting{%diff{converting $ to type $|converting between types}0,1}"
"|%Initializing{%diff{initializing $ with an expression of type $|"
"initializing with expression of different type}0,1}"
"|%Sending{%diff{sending $ to parameter of type $|"
"sending to parameter of different type}0,1}"
"|%Casting{%diff{casting $ to type $|casting between types}0,1}}2"
" changes pointer authentication of pointee type">;
def err_typecheck_comparison_of_distinct_blocks : Error<
"comparison of distinct block types%diff{ ($ and $)|}0,1">;

Expand Down Expand Up @@ -9083,6 +9117,9 @@ def err_atomic_op_needs_non_const_atomic : Error<
def err_atomic_op_needs_non_const_pointer : Error<
"address argument to atomic operation must be a pointer to non-const "
"type (%0 invalid)">;
def err_atomic_op_needs_non_address_discriminated_pointer : Error<
"address argument to %select{atomic|__sync}0 operation must be a pointer to a non address discriminated "
"type (%1 invalid)">;
def err_atomic_op_needs_trivial_copy : Error<
"address argument to atomic operation must be a pointer to a "
"trivially-copyable type (%0 invalid)">;
Expand Down Expand Up @@ -9358,6 +9395,8 @@ def ext_typecheck_cond_pointer_integer_mismatch : ExtWarn<
"pointer/integer type mismatch in conditional expression"
"%diff{ ($ and $)|}0,1">,
InGroup<DiagGroup<"conditional-type-mismatch">>;
def err_typecheck_cond_incompatible_ptrauth : Error<
"'__ptrauth' qualification mismatch%diff{ ($ and $)|}0,1">;
def err_typecheck_choose_expr_requires_constant : Error<
"'__builtin_choose_expr' requires a constant expression">;
def warn_unused_expr : Warning<"expression result unused">,
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ FEATURE(thread_sanitizer, LangOpts.Sanitize.has(SanitizerKind::Thread))
FEATURE(dataflow_sanitizer, LangOpts.Sanitize.has(SanitizerKind::DataFlow))
FEATURE(scudo, LangOpts.Sanitize.hasOneOf(SanitizerKind::Scudo))
FEATURE(ptrauth_intrinsics, LangOpts.PointerAuthIntrinsics)
EXTENSION(ptrauth_qualifier, LangOpts.PointerAuthIntrinsics)
FEATURE(ptrauth_calls, LangOpts.PointerAuthCalls)
FEATURE(ptrauth_returns, LangOpts.PointerAuthReturns)
FEATURE(ptrauth_vtable_pointer_address_discrimination, LangOpts.PointerAuthVTPtrAddressDiscrimination)
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ KEYWORD(_Thread_local , KEYALL)
KEYWORD(__func__ , KEYALL)
KEYWORD(__objc_yes , KEYALL)
KEYWORD(__objc_no , KEYALL)
KEYWORD(__ptrauth , KEYALL)


// C++ 2.11p1: Keywords.
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -3161,6 +3161,8 @@ class Parser : public CodeCompletionHandler {
SourceLocation *endLoc = nullptr);
ExprResult ParseExtIntegerArgument();

void ParsePtrauthQualifier(ParsedAttributes &Attrs);

VirtSpecifiers::Specifier isCXX11VirtSpecifier(const Token &Tok) const;
VirtSpecifiers::Specifier isCXX11VirtSpecifier() const {
return isCXX11VirtSpecifier(Tok);
Expand Down
11 changes: 11 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -3463,6 +3463,17 @@ class Sema final : public SemaBase {

bool checkConstantPointerAuthKey(Expr *keyExpr, unsigned &key);

enum PointerAuthDiscArgKind {
// Address discrimination argument of __ptrauth.
PADAK_AddrDiscPtrAuth,

// Extra discriminator argument of __ptrauth.
PADAK_ExtraDiscPtrAuth,
};

bool checkPointerAuthDiscriminatorArg(Expr *Arg, PointerAuthDiscArgKind Kind,
unsigned &IntVal);

/// Diagnose function specifiers on a declaration of an identifier that
/// does not identify a function.
void DiagnoseFunctionSpecifiers(const DeclSpec &DS);
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11452,6 +11452,7 @@ QualType ASTContext::mergeTypes(QualType LHS, QualType RHS, bool OfBlockPointer,
if (LQuals.getCVRQualifiers() != RQuals.getCVRQualifiers() ||
LQuals.getAddressSpace() != RQuals.getAddressSpace() ||
LQuals.getObjCLifetime() != RQuals.getObjCLifetime() ||
!LQuals.getPointerAuth().isEquivalent(RQuals.getPointerAuth()) ||
LQuals.hasUnaligned() != RQuals.hasUnaligned())
return {};

Expand Down
27 changes: 27 additions & 0 deletions clang/lib/AST/DeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1118,6 +1118,33 @@ void CXXRecordDecl::addedMember(Decl *D) {
} else if (!T.isCXX98PODType(Context))
data().PlainOldData = false;

// If a class has an address-discriminated signed pointer member, it is a
// non-POD type and its copy constructor, move constructor, copy assignment
// operator, move assignment operator are non-trivial.
if (PointerAuthQualifier Q = T.getPointerAuth()) {
if (Q.isAddressDiscriminated()) {
struct DefinitionData &Data = data();
Data.PlainOldData = false;
Data.HasTrivialSpecialMembers &=
~(SMF_CopyConstructor | SMF_MoveConstructor | SMF_CopyAssignment |
SMF_MoveAssignment);
setArgPassingRestrictions(RecordArgPassingKind::CanNeverPassInRegs);

// Copy/move constructors/assignment operators of a union are deleted by
// default if it has an address-discriminated ptrauth field.
if (isUnion()) {
data().DefaultedCopyConstructorIsDeleted = true;
data().DefaultedMoveConstructorIsDeleted = true;
data().DefaultedCopyAssignmentIsDeleted = true;
data().DefaultedMoveAssignmentIsDeleted = true;
data().NeedOverloadResolutionForCopyConstructor = true;
data().NeedOverloadResolutionForMoveConstructor = true;
data().NeedOverloadResolutionForCopyAssignment = true;
data().NeedOverloadResolutionForMoveAssignment = true;
}
}
}

if (Field->hasAttr<ExplicitInitAttr>())
setHasUninitializedExplicitInitFields(true);

Expand Down
20 changes: 20 additions & 0 deletions clang/lib/AST/ItaniumMangle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2850,6 +2850,26 @@ void CXXNameMangler::mangleQualifiers(Qualifiers Quals, const DependentAddressSp
if (Quals.hasUnaligned())
mangleVendorQualifier("__unaligned");

// __ptrauth. Note that this is parameterized.
if (PointerAuthQualifier PtrAuth = Quals.getPointerAuth()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We seem to be missing mangling for Microsoft; should we be using a vendor mangling there?

Copy link
Contributor

Choose a reason for hiding this comment

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

@AaronBallman we'd need MS to decide what their mangling would be - if it is to be different from this, or if MS ever want to support ptrauth?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Oops, I missed the question that was here, but this does still need to be addressed.

We don't need MS to be involved, we can pick our own mangling and try to avoid potential conflicts by making it ugly (we do this for plenty of other extensions). But we're missing test coverage because we have no C++ tests for mangling or demangling (Microsoft or Itanium). I think we're missing the demangling implementation entirely, but we lately have been trying to get folks to do them at the same time so the demangler stays closer in sync with the mangler.

(Test coverage would be: CodeGen tests with an Itanium and a Microsoft ABI making sure we emit the expected manglings in C++ with a FileCheck test + llvm/test/Demangle tests to make sure the generated manglings will demangle back to an expected signature.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Demangling already works for Itanium, though the qualifier might appear a little odd (it resembles a template instantiation).

For example:

$ ./bin/llvm-cxxfilt __Z3fooPU9__ptrauthILj1ELb0ELj23EEPi
foo(int* __ptrauth<1u, false, 23u>*)

mangleVendorQualifier("__ptrauth");
// For now, since we only allow non-dependent arguments, we can just
// inline the mangling of those arguments as literals. We treat the
// key and extra-discriminator arguments as 'unsigned int' and the
// address-discriminated argument as 'bool'.
Out << "I"
"Lj"
<< PtrAuth.getKey()
<< "E"
"Lb"
<< unsigned(PtrAuth.isAddressDiscriminated())
<< "E"
"Lj"
<< PtrAuth.getExtraDiscriminator()
<< "E"
"E";
}

// Remaining ARC ownership qualifiers.
switch (Quals.getObjCLifetime()) {
case Qualifiers::OCL_None:
Expand Down
Loading
Loading