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

Conversation

ahatanak
Copy link
Collaborator

The qualifier allows programmer to directly control how pointers are signed when they are stored in a particular variable.

The qualifier takes three arguments: the signing key, a flag specifying whether address discrimination should be used, and a non-negative integer that is used for additional discrimination.

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

Co-Authored-By: John McCall [email protected]

ahatanak added 5 commits July 26, 2024 15:58
The qualifier allows programmer to directly control how pointers are
signed when they are stored in a particular variable.

The qualifier takes three arguments: the signing key, a flag specifying
whether address discrimination should be used, and a non-negative
integer that is used for additional discrimination.

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

Co-Authored-By: John McCall [email protected]
Improve docs and comments. Add a test for the type printer.
Copy link

github-actions bot commented Aug 1, 2024

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff HEAD~1 HEAD --extensions c,h,cpp -- clang/test/CodeGen/ptrauth-debuginfo.c clang/test/CodeGen/ptrauth-qualifier-const-init.c clang/test/CodeGen/ptrauth-qualifier-function.c clang/test/CodeGen/ptrauth-qualifier-loadstore.c clang/test/CodeGenCXX/mangle-itanium-ptrauth.cpp clang/test/CodeGenCXX/mangle-ms-ptrauth.cpp clang/test/CodeGenCXX/ptrauth-qualifier-struct.cpp clang/test/Parser/ptrauth-qualifier.c clang/test/Preprocessor/ptrauth_extension.c clang/test/Sema/ptrauth-atomic-ops.c clang/test/Sema/ptrauth-qualifier.c clang/test/SemaCXX/ptrauth-qualifier.cpp clang/test/SemaCXX/ptrauth-template-parameters.cpp clang/include/clang/AST/Type.h clang/include/clang/Parse/Parser.h clang/include/clang/Sema/Sema.h clang/lib/AST/ASTContext.cpp clang/lib/AST/DeclCXX.cpp clang/lib/AST/ItaniumMangle.cpp clang/lib/AST/MicrosoftMangle.cpp clang/lib/AST/TypePrinter.cpp clang/lib/CodeGen/CGClass.cpp clang/lib/CodeGen/CGDebugInfo.cpp clang/lib/CodeGen/CGDecl.cpp clang/lib/CodeGen/CGExpr.cpp clang/lib/CodeGen/CGExprConstant.cpp clang/lib/CodeGen/CGExprScalar.cpp clang/lib/CodeGen/CGPointerAuth.cpp clang/lib/CodeGen/CodeGenFunction.h clang/lib/Parse/ParseDecl.cpp clang/lib/Sema/SemaCast.cpp clang/lib/Sema/SemaChecking.cpp clang/lib/Sema/SemaDecl.cpp clang/lib/Sema/SemaDeclCXX.cpp clang/lib/Sema/SemaExpr.cpp clang/lib/Sema/SemaExprCXX.cpp clang/lib/Sema/SemaObjCProperty.cpp clang/lib/Sema/SemaOverload.cpp clang/lib/Sema/SemaType.cpp clang/lib/Sema/TreeTransform.h clang/test/AST/ast-dump-ptrauth-json.cpp libcxxabi/test/test_demangle.pass.cpp llvm/include/llvm/Demangle/MicrosoftDemangle.h llvm/include/llvm/Demangle/MicrosoftDemangleNodes.h llvm/lib/Demangle/MicrosoftDemangle.cpp llvm/lib/Demangle/MicrosoftDemangleNodes.cpp
View the diff from clang-format here.
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index abb884770..8b05077fe 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1804,13 +1804,13 @@ CodeGenFunction::tryEmitAsConstant(const DeclRefExpr *RefExpr) {
     resultIsReference = false;
     resultType = RefExpr->getType().getUnqualifiedType();
 
-  // Otherwise, try to evaluate as an l-value.
+    // Otherwise, try to evaluate as an l-value.
   } else if (CEK != CEK_AsValueOnly &&
              RefExpr->EvaluateAsLValue(result, getContext())) {
     resultIsReference = true;
     resultType = Value->getType();
 
-  // Failure.
+    // Failure.
   } else {
     return ConstantEmission();
   }

@ahatanak ahatanak marked this pull request as ready for review August 2, 2024 02:07
@ahatanak ahatanak requested a review from Endilll as a code owner August 2, 2024 02:07
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. debuginfo labels Aug 2, 2024
@llvmbot
Copy link
Member

llvmbot commented Aug 2, 2024

@llvm/pr-subscribers-debuginfo

@llvm/pr-subscribers-clang

Author: Akira Hatanaka (ahatanak)

Changes

The qualifier allows programmer to directly control how pointers are signed when they are stored in a particular variable.

The qualifier takes three arguments: the signing key, a flag specifying whether address discrimination should be used, and a non-negative integer that is used for additional discrimination.

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

Co-Authored-By: John McCall [email protected]


Patch is 149.93 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/100830.diff

45 Files Affected:

  • (modified) clang/docs/PointerAuthentication.rst (+46)
  • (modified) clang/include/clang/AST/Type.h (+17-4)
  • (modified) clang/include/clang/Basic/Attr.td (+8)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+28)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+44-2)
  • (modified) clang/include/clang/Basic/Features.def (+1)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+1)
  • (modified) clang/include/clang/Parse/Parser.h (+2)
  • (modified) clang/include/clang/Sema/Sema.h (+11)
  • (modified) clang/lib/AST/ASTContext.cpp (+1)
  • (modified) clang/lib/AST/DeclCXX.cpp (+27)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+20)
  • (modified) clang/lib/AST/TypePrinter.cpp (+40)
  • (modified) clang/lib/CodeGen/CGClass.cpp (+3)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+17-2)
  • (modified) clang/lib/CodeGen/CGDecl.cpp (+9-3)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+59-2)
  • (modified) clang/lib/CodeGen/CGExprConstant.cpp (+21-3)
  • (modified) clang/lib/CodeGen/CGExprScalar.cpp (+67)
  • (modified) clang/lib/CodeGen/CGPointerAuth.cpp (+130)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+20)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+47)
  • (modified) clang/lib/Sema/SemaCast.cpp (+15-1)
  • (modified) clang/lib/Sema/SemaChecking.cpp (+63)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+13-1)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+37)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+14)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+5)
  • (modified) clang/lib/Sema/SemaObjCProperty.cpp (+4)
  • (modified) clang/lib/Sema/SemaOverload.cpp (+11)
  • (modified) clang/lib/Sema/SemaType.cpp (+87)
  • (modified) clang/lib/Sema/TreeTransform.h (+11)
  • (modified) clang/test/AST/ast-dump-ptrauth-json.cpp (+2)
  • (added) clang/test/CodeGen/ptrauth-debuginfo.c (+32)
  • (added) clang/test/CodeGen/ptrauth-qualifier-function.c (+132)
  • (added) clang/test/CodeGen/ptrauth-qualifier-loadstore.c (+744)
  • (added) clang/test/CodeGen/ptrauth-qualifier.c (+87)
  • (added) clang/test/CodeGenCXX/ptrauth-qualifier-struct.cpp (+167)
  • (added) clang/test/CodeGenObjCXX/ptrauth-struct-cxx-abi.mm (+35)
  • (modified) clang/test/Preprocessor/ptrauth_feature.c (+8)
  • (added) clang/test/Sema/ptrauth-atomic-ops.c (+117)
  • (added) clang/test/Sema/ptrauth-qualifier.c (+84)
  • (added) clang/test/SemaCXX/ptrauth-qualifier.cpp (+141)
  • (added) clang/test/SemaCXX/ptrauth-template-parameters.cpp (+20)
  • (added) clang/test/SemaObjC/ptrauth-qualifier.m (+55)
diff --git a/clang/docs/PointerAuthentication.rst b/clang/docs/PointerAuthentication.rst
index 68674f318c84f..41818d43ac687 100644
--- a/clang/docs/PointerAuthentication.rst
+++ b/clang/docs/PointerAuthentication.rst
@@ -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>``
 ~~~~~~~~~~~~~~~
 
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 72723c7c56e07..2fb17f352f1b3 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -307,6 +307,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); }
 };
 
@@ -556,7 +562,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());
@@ -815,6 +821,9 @@ 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_t(0xffffffff) << PtrAuthShift;
+
   static constexpr uint64_t UMask = 0x8;
   static constexpr uint64_t UShift = 3;
   static constexpr uint64_t GCAttrMask = 0x30;
@@ -822,10 +831,8 @@ class Qualifiers {
   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 {
@@ -1460,6 +1467,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
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 4825979a974d2..f9b447e85707d 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3339,6 +3339,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>];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 99738812c8157..e7b990a2abb8c 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -1758,6 +1758,34 @@ Also see the documentation for `@available
   }];
 }
 
+def PtrAuthDocs : Documentation {
+  let Category = DocCatVariable;
+  let Heading = "__ptrauth, __ptrauth_restricted_intptr";
+  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 small non-negative integer
+which allows additional discrimination between objects.
+  }];
+}
+
 def ExternalSourceSymbolDocs : Documentation {
   let Category = DocCatDecl;
   let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 810abe4f23e31..db07874ffcbbf 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -956,6 +956,25 @@ 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 types|parameter types|properties}2 may not be qualified with %select{__ptrauth|__ptrauth_restricted_intptr}1; type is %0">;
+def err_ptrauth_qualifier_cast : Error<
+  "cast types may not be qualified with __ptrauth; type is %0">;
+def err_ptrauth_qualifier_nonpointer : Error<
+  "__ptrauth qualifier may only be applied to pointer types; type here is %0">;
+def err_ptrauth_qualifier_redundant : Error<
+  "type %0 is already %1-qualified">;
+def err_ptrauth_qualifier_bad_arg_count : Error<
+  "%0 qualifier must take between 1 and 3 arguments">;
+def err_ptrauth_arg_not_ice : Error<
+  "argument to __ptrauth must be an integer constant expression">;
+def err_ptrauth_address_discrimination_invalid : Error<
+  "address discrimination flag for __ptrauth must be 0 or 1; value is %0">;
+def err_ptrauth_extra_discriminator_invalid : Error<
+  "extra discriminator for __ptrauth must be between "
+  "0 and %1; value is %0">;
+
 /// main()
 // static main() is not an error in C, just in C++.
 def warn_static_main : Warning<"'main' should not be declared static">,
@@ -3870,7 +3889,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<
@@ -4927,6 +4947,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 "
@@ -6005,7 +6029,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_deleted_default_ctor_uninit_field : Note<
   "%select{default constructor of|constructor inherited by}0 "
   "%1 is implicitly deleted because field %2 of "
@@ -8811,6 +8835,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<
+  "%select{%diff{assigning $ to $|assigning to different types}1,0"
+  "|%diff{passing $ to parameter of type $|"
+  "passing to parameter of different type}0,1"
+  "|%diff{returning $ from a function with result type $|"
+  "returning from function with different return type}0,1"
+  "|%diff{converting $ to type $|converting between types}0,1"
+  "|%diff{initializing $ with an expression of type $|"
+  "initializing with expression of different type}0,1"
+  "|%diff{sending $ to parameter of type $|"
+  "sending to parameter of different type}0,1"
+  "|%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">;
 
@@ -8939,6 +8976,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)">;
@@ -9205,6 +9245,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">,
diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def
index dc71ef8f98692..6853a7969e929 100644
--- a/clang/include/clang/Basic/Features.def
+++ b/clang/include/clang/Basic/Features.def
@@ -104,6 +104,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)
+FEATURE(ptrauth_qualifier, LangOpts.PointerAuthIntrinsics)
 FEATURE(ptrauth_calls, LangOpts.PointerAuthCalls)
 FEATURE(ptrauth_returns, LangOpts.PointerAuthReturns)
 FEATURE(ptrauth_vtable_pointer_address_discrimination, LangOpts.PointerAuthVTPtrAddressDiscrimination)
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 8c54661e65cf4..68a214452e28b 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -346,6 +346,7 @@ KEYWORD(_Thread_local               , KEYALL)
 KEYWORD(__func__                    , KEYALL)
 KEYWORD(__objc_yes                  , KEYALL)
 KEYWORD(__objc_no                   , KEYALL)
+KEYWORD(__ptrauth                   , KEYALL)
 
 
 // C++ 2.11p1: Keywords.
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 35bb1a19d40f0..e3049914289bd 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3151,6 +3151,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);
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 7bfdaaae45a93..673862e4711a4 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3460,6 +3460,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);
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index a465cdfcf3c89..c1dc730eaa2d5 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -11046,6 +11046,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 {};
 
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index b573c2713a3aa..c2c550076ea07 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -1072,6 +1072,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 (T->isReferenceType()) {
       if (!Field->hasInClassInitializer())
         data().HasUninitializedReferenceMember = true;
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index d46d621d4c7d4..0e95517bfa424 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -2822,6 +2822,26 @@ void CXXNameMangler::mangleQualifiers(Qualifiers Quals, const DependentAddressSp
   if (Quals.hasUnaligned())
     mangleVendorQualifier("__unaligned");
 
+  // __ptrauth.  Note that this is parameterized.
+  if (PointerAuthQualifier PtrAuth = Quals.getPointerAuth()) {
+    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:
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index ffec3ef9d2269..542cf7502e20e 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1950,6 +1950,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::Ptr64:
   case attr::SPtr:
   case attr::UPtr:
+  case attr::PointerAuth:
   case attr::AddressSpace:
   case attr::CmseNSCall:
   case attr::AnnotateType:
@@ -2413,6 +2414,33 @@ void clang::printTemplateArgumentList(raw_ostream &OS,
   printTo(OS, Args, Policy, TPL, /*isPack*/ false, /*parmIndex*/ 0);
 }
 
+std::string PointerAuthQualifier::getAsString() const {
+  LangOptions LO;
+  return getAsString(PrintingPolicy(LO));
+}
+
+std::string PointerAuthQualifier::getAsString(const PrintingPolicy &P) const {
+  SmallString<64> Buf;
+  llvm::raw_svector_ostream StrOS(Buf);
+  print(StrOS, P);
+  return StrOS.str().str();
+}
+
+bool PointerAuthQualifier::isEmptyWhenPrinted(const PrintingPolicy &P) const {
+  return !isPresent();
+}
+
+void PointerAuthQualifier::print(raw_ostream &OS,
+                                 const PrintingPolicy &P) const {
+  if (!isPresent())
+    return;
+
+  OS << "__ptrauth(";
+  OS << getKey();
+  OS << "," << unsigned(isAddressDiscriminated()) << ","
+     << getExtraDiscriminator() << ")";
+}
+
 std::string Qualifiers::getAsString() const {
   LangOptions LO;
   return getAsString(PrintingPolicy(LO));
@@ -2442,6 +2470,10 @@ bool Qualifiers::isEmptyWhenPrinted(const PrintingPolicy &Policy) const {
     if (!(lifetime == Qualifiers::OCL_Strong && Policy.Suppres...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Aug 2, 2024

@llvm/pr-subscribers-clang-codegen

Author: Akira Hatanaka (ahatanak)

Changes

The qualifier allows programmer to directly control how pointers are signed when they are stored in a particular variable.

The qualifier takes three arguments: the signing key, a flag specifying whether address discrimination should be used, and a non-negative integer that is used for additional discrimination.

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

Co-Authored-By: John McCall [email protected]


Patch is 149.93 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/100830.diff

45 Files Affected:

  • (modified) clang/docs/PointerAuthentication.rst (+46)
  • (modified) clang/include/clang/AST/Type.h (+17-4)
  • (modified) clang/include/clang/Basic/Attr.td (+8)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+28)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+44-2)
  • (modified) clang/include/clang/Basic/Features.def (+1)
  • (modified) clang/include/clang/Basic/TokenKinds.def (+1)
  • (modified) clang/include/clang/Parse/Parser.h (+2)
  • (modified) clang/include/clang/Sema/Sema.h (+11)
  • (modified) clang/lib/AST/ASTContext.cpp (+1)
  • (modified) clang/lib/AST/DeclCXX.cpp (+27)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+20)
  • (modified) clang/lib/AST/TypePrinter.cpp (+40)
  • (modified) clang/lib/CodeGen/CGClass.cpp (+3)
  • (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+17-2)
  • (modified) clang/lib/CodeGen/CGDecl.cpp (+9-3)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+59-2)
  • (modified) clang/lib/CodeGen/CGExprConstant.cpp (+21-3)
  • (modified) clang/lib/CodeGen/CGExprScalar.cpp (+67)
  • (modified) clang/lib/CodeGen/CGPointerAuth.cpp (+130)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+20)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+47)
  • (modified) clang/lib/Sema/SemaCast.cpp (+15-1)
  • (modified) clang/lib/Sema/SemaChecking.cpp (+63)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+13-1)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+37)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+14)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+5)
  • (modified) clang/lib/Sema/SemaObjCProperty.cpp (+4)
  • (modified) clang/lib/Sema/SemaOverload.cpp (+11)
  • (modified) clang/lib/Sema/SemaType.cpp (+87)
  • (modified) clang/lib/Sema/TreeTransform.h (+11)
  • (modified) clang/test/AST/ast-dump-ptrauth-json.cpp (+2)
  • (added) clang/test/CodeGen/ptrauth-debuginfo.c (+32)
  • (added) clang/test/CodeGen/ptrauth-qualifier-function.c (+132)
  • (added) clang/test/CodeGen/ptrauth-qualifier-loadstore.c (+744)
  • (added) clang/test/CodeGen/ptrauth-qualifier.c (+87)
  • (added) clang/test/CodeGenCXX/ptrauth-qualifier-struct.cpp (+167)
  • (added) clang/test/CodeGenObjCXX/ptrauth-struct-cxx-abi.mm (+35)
  • (modified) clang/test/Preprocessor/ptrauth_feature.c (+8)
  • (added) clang/test/Sema/ptrauth-atomic-ops.c (+117)
  • (added) clang/test/Sema/ptrauth-qualifier.c (+84)
  • (added) clang/test/SemaCXX/ptrauth-qualifier.cpp (+141)
  • (added) clang/test/SemaCXX/ptrauth-template-parameters.cpp (+20)
  • (added) clang/test/SemaObjC/ptrauth-qualifier.m (+55)
diff --git a/clang/docs/PointerAuthentication.rst b/clang/docs/PointerAuthentication.rst
index 68674f318c84f..41818d43ac687 100644
--- a/clang/docs/PointerAuthentication.rst
+++ b/clang/docs/PointerAuthentication.rst
@@ -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>``
 ~~~~~~~~~~~~~~~
 
diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h
index 72723c7c56e07..2fb17f352f1b3 100644
--- a/clang/include/clang/AST/Type.h
+++ b/clang/include/clang/AST/Type.h
@@ -307,6 +307,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); }
 };
 
@@ -556,7 +562,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());
@@ -815,6 +821,9 @@ 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_t(0xffffffff) << PtrAuthShift;
+
   static constexpr uint64_t UMask = 0x8;
   static constexpr uint64_t UShift = 3;
   static constexpr uint64_t GCAttrMask = 0x30;
@@ -822,10 +831,8 @@ class Qualifiers {
   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 {
@@ -1460,6 +1467,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
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 4825979a974d2..f9b447e85707d 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3339,6 +3339,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>];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 99738812c8157..e7b990a2abb8c 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -1758,6 +1758,34 @@ Also see the documentation for `@available
   }];
 }
 
+def PtrAuthDocs : Documentation {
+  let Category = DocCatVariable;
+  let Heading = "__ptrauth, __ptrauth_restricted_intptr";
+  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 small non-negative integer
+which allows additional discrimination between objects.
+  }];
+}
+
 def ExternalSourceSymbolDocs : Documentation {
   let Category = DocCatDecl;
   let Content = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 810abe4f23e31..db07874ffcbbf 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -956,6 +956,25 @@ 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 types|parameter types|properties}2 may not be qualified with %select{__ptrauth|__ptrauth_restricted_intptr}1; type is %0">;
+def err_ptrauth_qualifier_cast : Error<
+  "cast types may not be qualified with __ptrauth; type is %0">;
+def err_ptrauth_qualifier_nonpointer : Error<
+  "__ptrauth qualifier may only be applied to pointer types; type here is %0">;
+def err_ptrauth_qualifier_redundant : Error<
+  "type %0 is already %1-qualified">;
+def err_ptrauth_qualifier_bad_arg_count : Error<
+  "%0 qualifier must take between 1 and 3 arguments">;
+def err_ptrauth_arg_not_ice : Error<
+  "argument to __ptrauth must be an integer constant expression">;
+def err_ptrauth_address_discrimination_invalid : Error<
+  "address discrimination flag for __ptrauth must be 0 or 1; value is %0">;
+def err_ptrauth_extra_discriminator_invalid : Error<
+  "extra discriminator for __ptrauth must be between "
+  "0 and %1; value is %0">;
+
 /// main()
 // static main() is not an error in C, just in C++.
 def warn_static_main : Warning<"'main' should not be declared static">,
@@ -3870,7 +3889,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<
@@ -4927,6 +4947,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 "
@@ -6005,7 +6029,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_deleted_default_ctor_uninit_field : Note<
   "%select{default constructor of|constructor inherited by}0 "
   "%1 is implicitly deleted because field %2 of "
@@ -8811,6 +8835,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<
+  "%select{%diff{assigning $ to $|assigning to different types}1,0"
+  "|%diff{passing $ to parameter of type $|"
+  "passing to parameter of different type}0,1"
+  "|%diff{returning $ from a function with result type $|"
+  "returning from function with different return type}0,1"
+  "|%diff{converting $ to type $|converting between types}0,1"
+  "|%diff{initializing $ with an expression of type $|"
+  "initializing with expression of different type}0,1"
+  "|%diff{sending $ to parameter of type $|"
+  "sending to parameter of different type}0,1"
+  "|%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">;
 
@@ -8939,6 +8976,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)">;
@@ -9205,6 +9245,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">,
diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def
index dc71ef8f98692..6853a7969e929 100644
--- a/clang/include/clang/Basic/Features.def
+++ b/clang/include/clang/Basic/Features.def
@@ -104,6 +104,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)
+FEATURE(ptrauth_qualifier, LangOpts.PointerAuthIntrinsics)
 FEATURE(ptrauth_calls, LangOpts.PointerAuthCalls)
 FEATURE(ptrauth_returns, LangOpts.PointerAuthReturns)
 FEATURE(ptrauth_vtable_pointer_address_discrimination, LangOpts.PointerAuthVTPtrAddressDiscrimination)
diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def
index 8c54661e65cf4..68a214452e28b 100644
--- a/clang/include/clang/Basic/TokenKinds.def
+++ b/clang/include/clang/Basic/TokenKinds.def
@@ -346,6 +346,7 @@ KEYWORD(_Thread_local               , KEYALL)
 KEYWORD(__func__                    , KEYALL)
 KEYWORD(__objc_yes                  , KEYALL)
 KEYWORD(__objc_no                   , KEYALL)
+KEYWORD(__ptrauth                   , KEYALL)
 
 
 // C++ 2.11p1: Keywords.
diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h
index 35bb1a19d40f0..e3049914289bd 100644
--- a/clang/include/clang/Parse/Parser.h
+++ b/clang/include/clang/Parse/Parser.h
@@ -3151,6 +3151,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);
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 7bfdaaae45a93..673862e4711a4 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -3460,6 +3460,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);
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index a465cdfcf3c89..c1dc730eaa2d5 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -11046,6 +11046,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 {};
 
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index b573c2713a3aa..c2c550076ea07 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -1072,6 +1072,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 (T->isReferenceType()) {
       if (!Field->hasInClassInitializer())
         data().HasUninitializedReferenceMember = true;
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index d46d621d4c7d4..0e95517bfa424 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -2822,6 +2822,26 @@ void CXXNameMangler::mangleQualifiers(Qualifiers Quals, const DependentAddressSp
   if (Quals.hasUnaligned())
     mangleVendorQualifier("__unaligned");
 
+  // __ptrauth.  Note that this is parameterized.
+  if (PointerAuthQualifier PtrAuth = Quals.getPointerAuth()) {
+    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:
diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp
index ffec3ef9d2269..542cf7502e20e 100644
--- a/clang/lib/AST/TypePrinter.cpp
+++ b/clang/lib/AST/TypePrinter.cpp
@@ -1950,6 +1950,7 @@ void TypePrinter::printAttributedAfter(const AttributedType *T,
   case attr::Ptr64:
   case attr::SPtr:
   case attr::UPtr:
+  case attr::PointerAuth:
   case attr::AddressSpace:
   case attr::CmseNSCall:
   case attr::AnnotateType:
@@ -2413,6 +2414,33 @@ void clang::printTemplateArgumentList(raw_ostream &OS,
   printTo(OS, Args, Policy, TPL, /*isPack*/ false, /*parmIndex*/ 0);
 }
 
+std::string PointerAuthQualifier::getAsString() const {
+  LangOptions LO;
+  return getAsString(PrintingPolicy(LO));
+}
+
+std::string PointerAuthQualifier::getAsString(const PrintingPolicy &P) const {
+  SmallString<64> Buf;
+  llvm::raw_svector_ostream StrOS(Buf);
+  print(StrOS, P);
+  return StrOS.str().str();
+}
+
+bool PointerAuthQualifier::isEmptyWhenPrinted(const PrintingPolicy &P) const {
+  return !isPresent();
+}
+
+void PointerAuthQualifier::print(raw_ostream &OS,
+                                 const PrintingPolicy &P) const {
+  if (!isPresent())
+    return;
+
+  OS << "__ptrauth(";
+  OS << getKey();
+  OS << "," << unsigned(isAddressDiscriminated()) << ","
+     << getExtraDiscriminator() << ")";
+}
+
 std::string Qualifiers::getAsString() const {
   LangOptions LO;
   return getAsString(PrintingPolicy(LO));
@@ -2442,6 +2470,10 @@ bool Qualifiers::isEmptyWhenPrinted(const PrintingPolicy &Policy) const {
     if (!(lifetime == Qualifiers::OCL_Strong && Policy.Suppres...
[truncated]

@Endilll Endilll removed their request for review January 15, 2025 18:43
@ahatanak
Copy link
Collaborator Author

Any other feedback on this patch?

For those who haven't followed the RFC, the RFC received positive reviews, and we have reached a consensus to move forward with the proposal. https://discourse.llvm.org/t/rfc-ptrauth-qualifier/80710/31

@kovdan01
Copy link
Contributor

Any other feedback on this patch?

For those who haven't followed the RFC, the RFC received positive reviews, and we have reached a consensus to move forward with the proposal. https://discourse.llvm.org/t/rfc-ptrauth-qualifier/80710/31

@ahatanak I personally have no new feedback compared to what I've already commented on several months ago, so it looks good to me if someone with pauth context in mind approves it.

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

Thank you for this! Please be sure to add a release note to clang/docs/ReleaseNotes.rst as we get closer to finalizing on the PR so users know about the feature.

I started a pass over the PR but ran out of time before I could get to Parse and Sema, so there's more to come at some point.

@ahatanak
Copy link
Collaborator Author

ahatanak commented Mar 4, 2025

@AaronBallman do you have more feedback?

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

I got through the rest of parser and sema, thank you for your patience! I think we should add quite a bit more testing for all the places where types are important in both C and C++; the test coverage we have right now is a good start, but misses a lot of the corner cases of the languages.

test_bad_call_diag(&ptr2); // expected-error {{no matching function for call to 'test_bad_call_diag'}}
test_bad_call_diag(&ptr3); // expected-error {{no matching function for call to 'test_bad_call_diag'}}
test_bad_call_diag2(&ptr1); // expected-error {{no matching function for call to 'test_bad_call_diag2'}}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Other test cases we should consider in C++: lambdas and their captures (since we have special rules about fields in structures), concept checking, use in constant expressions (particularly, catching UB and ensuring that's rejected), function overloading.

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 wasn’t sure what kinds of tests you had in mind beyond those for function overloading.

Would you like a CodeGen test that verifies authentication of captured __ptrauth-qualified variables? For example:

  int * __ptrauth(1,0,101) p;

  int i = [=]{
    return *p;
  }();

Additionally, what specific tests are needed for concept checking and constant expressions?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@AaronBallman could you elaborate on what kinds of tests you are looking for?

Copy link
Collaborator

Choose a reason for hiding this comment

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

In terms of constant expressions, there's two things I think we need to test:

  1. Correct uses of pointers with authentication qualifiers on them; basically, validating that code without the qualifiers on pointers and code with the correct qualifiers on pointer both work in constant expressions.
  2. Any situation in which incorrect use of __ptrauth would result in undefined behavior rather than a compile time error. Basically, we want to make sure that constant expression evaluation continues to have the property that it's UB-free in a constant evaluation context.

In terms of lambdas, I was thinking things along the lines of:

  1. A test where we capture a ptrauth-qualified object and make sure it behaves as expected
  2. A test with an explicit-init capture of ptrauth qualified type to make sure the qualifier is picked up appropriately

As for concept checking, I was thinking something like:

template <typename T> struct is_qualified {
    static constexpr bool value = false;
};

template <typename T> struct is_qualified<T * __ptrauth(???)> {
    static constexpr bool value = true;
};

template <typename T>
concept Ptrauthable = is_qualified<T>::value;

template <typename T>
    requires(Ptrauthable<T>)
void func(T);

// Make calls to fund with a __ptrauth pointer and without, make sure concept checking works.

(Code example is wrong, but hopefully it gives you the right idea.)

Copy link
Contributor

Choose a reason for hiding this comment

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

We haven't had to write any constant evaluation code for pointer auth because the rules should already all be enforced at a higher level. Like, you can trigger pointer auth failures by casting e.g. void ** to void * __ptrauth(...) * and accessing with the wrong type, but that should already be rejected by the constant evaluator because it requires a reinterpret_cast (and if not that, a type-aliasing violation). But I agree that we should have tests for it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Other way, actually — hasSimilarType should only allow differences in CVR qualifiers. ARC, ptrauth, and address-space qualification differences are not similar.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry, I think I was misunderstanding the problem.

I was trying to understand why clang correctly rejects a similar example using address space.

For example:

constexpr int __attribute__((address_space(256))) i = 100;
constexpr const int __attribute__((address_space(256))) * p = &i;
constexpr const int __attribute__((address_space(256))) * const *pp = &p;
int array1[**((const int __attribute__((address_space(255))) * const *)pp)]; // note: cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression

The difference is that the CStyleCastExpr created has kind BitCast when address space is used, but has kind NoOp when __ptrauth is used. This happens because isQualificationConversionStep in SemaOverload.cpp doesn't check whether there's any difference in the __ptrauth qualifiers, which causes TryStaticImplicitCast to succeed, which in turn causes TryStaticCast to succeed (i.e., the c-style cast is performing static_cast, not reinterpret_cast).

hasSimilarType currently returns true when types with different address space or __ptrauth qualifiers are passed. This is possibly wrong, but we don't have to change this to make clang reject the test case involving __ptrauth.

Copy link
Contributor

Choose a reason for hiding this comment

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

That makes more sense, and it should be less dangerous to fix.

Type similarity is used in relatively few places in C++ semantic analysis, so it's quite believable that a bug there would stay latent. We should fix it just on principle, but it might be difficult to construct a test case that would be wrong.

Copy link
Collaborator

Choose a reason for hiding this comment

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

So are we planning to fix up hasSimilarType() as part of this PR, or in a follow-up given that we can't find a test case that would break?

Copy link
Contributor

Choose a reason for hiding this comment

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

If we were changing hasSimilarType in the patch anyway, I think we'd want to not knowingly commit incorrect code. But given that hasSimilarType is apparently broadly broken for non-CVR qualifiers, I think we should just fix it in a separate PR.

@ojhunt
Copy link
Contributor

ojhunt commented Apr 13, 2025

@AaronBallman do you have any further requests or changes for this PR?

@AaronBallman
Copy link
Collaborator

@AaronBallman do you have any further requests or changes for this PR?

The only outstanding issue I know of is: https://github.com/llvm/llvm-project/pull/100830/files#r2039375386

@ahatanak ahatanak requested a review from a team as a code owner April 15, 2025 01:23
Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

LGTM! The precommit CI failure appears to be unrelated.

@ahatanak ahatanak merged commit a3283a9 into llvm:main Apr 15, 2025
83 of 85 checks passed
@ahatanak ahatanak deleted the ptrauth-qualifier branch April 15, 2025 19:54
var-const pushed a commit to ldionne/llvm-project that referenced this pull request Apr 17, 2025
The qualifier allows programmer to directly control how pointers are
signed when they are stored in a particular variable.

The qualifier takes three arguments: the signing key, a flag specifying
whether address discrimination should be used, and a non-negative
integer that is used for additional discrimination.

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

Co-Authored-By: John McCall [email protected]
qiongsiwu added a commit to swiftlang/llvm-project that referenced this pull request Apr 22, 2025
…10513

This is a test case for llvm#133500.

This test case cannot be added to upstream llvm because upstream currently does not have the __ptrauth qualifier yet. llvm#100830 will add the qualifier, and we shall consider upstreaming this test case when llvm#100830 lands.

rdar://143763558

(cherry picked from commit 3af0d75)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category debuginfo
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants