Skip to content

[clang] Implement pointer authentication for C++ virtual functions, v-tables, and VTTs #94056

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 10 commits into from
Jun 27, 2024

Conversation

ojhunt
Copy link
Contributor

@ojhunt ojhunt commented May 31, 2024

Virtual function pointer entries in v-tables are signed with address discrimination in addition to declaration-based discrimination, where an integer discriminator the string hash (see ptrauth_string_discriminator) of the mangled name of the overridden method. This notably provides diversity based on the full signature of the overridden method, including the method name and parameter types.
This patch introduces ItaniumVTableContext logic to find the original declaration of the overridden method. On AArch64, these pointers are signed using the IA key (the process-independent code key.)

V-table pointers can be signed with either no discrimination, or a similar scheme using address and decl-based discrimination. In this case, the integer discriminator is the string hash of the mangled v-table identifier of the class that originally introduced the vtable pointer. On AArch64, these pointers are signed using the DA key (the process-independent data key.)

Not using discrimination allows attackers to simply copy valid v-table pointers from one object to another. However, using a uniform discriminator of 0 does have positive performance and code-size implications on AArch64, and diversity for the most important v-table access pattern (virtual dispatch) is already better assured by the signing schemas used on the virtual functions. It is also known that some code in practice copies objects containing v-tables with memcpy, and while this is not permitted formally, it is something that may be invasive to eliminate.

This is controlled by:

  -fptrauth-vtable-pointer-type-discrimination
  -fptrauth-vtable-pointer-address-discrimination

In addition, this provides fine-grained controls in the ptrauth_vtable_pointer attribute, which allows overriding the default ptrauth schema for vtable pointers on a given class hierarchy, e.g.:

  [[clang::ptrauth_vtable_pointer(no_authentication, no_address_discrimination, 
                                  no_extra_discrimination)]]
  [[clang::ptrauth_vtable_pointer(default_key, default_address_discrimination,
                                  custom_discrimination, 0xf00d)]]

The override is then mangled as a parametrized vendor extension:

"__vtptrauth" I
 <key>
 <addressDiscriminated>
 <extraDiscriminator>
E

To support this attribute, this patch adds a small extension to the attribute-emitter tablegen backend.

Note that there are known areas where signing is either missing altogether or can be strengthened. Some will be addressed in later changes (e.g., member function pointers, some RTTI).
dynamic_cast in particular is handled by emitting an artificial v-table pointer load (in a way that always authenticates it) before the runtime call itself, as the runtime doesn't have enough information today to properly authenticate it. Instead, the runtime is currently expected to strip the v-table pointer.

@ojhunt ojhunt marked this pull request as ready for review June 1, 2024 18:09
@llvmbot llvmbot added clang Clang issues not falling into any other category backend:X86 clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:headers Headers provided by Clang, e.g. for intrinsics clang:codegen IR generation bugs: mangling, exceptions, etc. labels Jun 1, 2024
@llvmbot
Copy link
Member

llvmbot commented Jun 1, 2024

@llvm/pr-subscribers-backend-aarch64
@llvm/pr-subscribers-backend-x86
@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-codegen

Author: Oliver Hunt (ojhunt)

Changes

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

53 Files Affected:

  • (modified) clang/include/clang/AST/ASTContext.h (+13)
  • (modified) clang/include/clang/AST/GlobalDecl.h (+4)
  • (modified) clang/include/clang/AST/Mangle.h (+5-6)
  • (modified) clang/include/clang/AST/VTableBuilder.h (+29)
  • (modified) clang/include/clang/Basic/Attr.td (+29)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+31)
  • (modified) clang/include/clang/Basic/PointerAuthOptions.h (+25)
  • (modified) clang/include/clang/Basic/Thunk.h (+10-3)
  • (modified) clang/include/clang/CodeGen/CodeGenABITypes.h (+4)
  • (modified) clang/include/clang/CodeGen/ConstantInitBuilder.h (+15-1)
  • (modified) clang/include/clang/InstallAPI/Visitor.h (+2-2)
  • (modified) clang/include/clang/Sema/Sema.h (+4)
  • (modified) clang/lib/AST/ASTContext.cpp (+86)
  • (modified) clang/lib/AST/ItaniumMangle.cpp (+70-7)
  • (modified) clang/lib/AST/Mangle.cpp (+17-6)
  • (modified) clang/lib/AST/MicrosoftMangle.cpp (+16-7)
  • (modified) clang/lib/AST/VTableBuilder.cpp (+101-6)
  • (modified) clang/lib/CodeGen/CGCXX.cpp (+10-1)
  • (modified) clang/lib/CodeGen/CGCXXABI.h (+8-6)
  • (modified) clang/lib/CodeGen/CGClass.cpp (+30-1)
  • (modified) clang/lib/CodeGen/CGExpr.cpp (+5-2)
  • (modified) clang/lib/CodeGen/CGExprConstant.cpp (+76-1)
  • (modified) clang/lib/CodeGen/CGPointerAuth.cpp (+239)
  • (modified) clang/lib/CodeGen/CGVTT.cpp (+5)
  • (modified) clang/lib/CodeGen/CGVTables.cpp (+38-10)
  • (modified) clang/lib/CodeGen/CodeGenFunction.cpp (+27)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+18-2)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+19)
  • (modified) clang/lib/CodeGen/ConstantEmitter.h (+3)
  • (modified) clang/lib/CodeGen/ItaniumCXXABI.cpp (+53-19)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+7-2)
  • (modified) clang/lib/Frontend/CompilerInvocation.cpp (+12)
  • (modified) clang/lib/Headers/ptrauth.h (+14)
  • (modified) clang/lib/InstallAPI/Visitor.cpp (+7-6)
  • (modified) clang/lib/Parse/ParseDecl.cpp (+29-1)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+116)
  • (modified) clang/lib/Sema/SemaDeclCXX.cpp (+46)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+33)
  • (added) clang/test/CodeGen/ptrauth-ubsan-vptr.cpp (+30)
  • (modified) clang/test/CodeGenCXX/catch-undef-behavior.cpp (+4-3)
  • (added) clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-call-2.cpp (+105)
  • (added) clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-call.cpp (+42)
  • (added) clang/test/CodeGenCXX/ptrauth-apple-kext-indirect-virtual-dtor-call.cpp (+50)
  • (added) clang/test/CodeGenCXX/ptrauth-explicit-vtable-pointer-control.cpp (+652)
  • (added) clang/test/CodeGenCXX/ptrauth-rtti-layout.cpp (+10)
  • (added) clang/test/CodeGenCXX/ptrauth-thunks.cpp (+28)
  • (added) clang/test/CodeGenCXX/ptrauth-virtual-function.cpp (+581)
  • (added) clang/test/CodeGenCXX/ptrauth-vtable-virtual-inheritance-thunk.cpp (+309)
  • (modified) clang/test/CodeGenCXX/ubsan-vtable-checks.cpp (+46-2)
  • (modified) clang/test/Misc/pragma-attribute-supported-attributes-list.test (+1)
  • (added) clang/test/SemaCXX/ptrauth-incomplete-virtual-member-function-return-arg-type.cpp (+50)
  • (added) clang/test/SemaCXX/vtable_pointer_authentication_attribute.cpp (+225)
  • (modified) clang/utils/TableGen/ClangAttrEmitter.cpp (+27)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index a1d1d1c51cd41..e601face9c2b6 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -37,6 +37,7 @@
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/StringSet.h"
 #include "llvm/ADT/TinyPtrVector.h"
 #include "llvm/Support/TypeSize.h"
 #include <optional>
@@ -1261,6 +1262,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// space.
   QualType removeAddrSpaceQualType(QualType T) const;
 
+  /// Return the "other" type-specific discriminator for the given type.
+  uint16_t
+  getPointerAuthVTablePointerDiscriminator(const CXXRecordDecl *record);
+
   /// Apply Objective-C protocol qualifiers to the given type.
   /// \param allowOnPointerType specifies if we can apply protocol
   /// qualifiers on ObjCObjectPointerType. It can be set to true when
@@ -3418,12 +3423,20 @@ OPT_LIST(V)
   /// Whether a C++ static variable or CUDA/HIP kernel should be externalized.
   bool shouldExternalize(const Decl *D) const;
 
+  /// Resolve the root record to be used to derive the vtable pointer
+  /// authentication policy for the specified record.
+  const CXXRecordDecl *baseForVTableAuthentication(const CXXRecordDecl *);
+  bool useAbbreviatedThunkName(GlobalDecl virtualMethodDecl,
+                               StringRef mangledName);
+
   StringRef getCUIDHash() const;
 
 private:
   /// All OMPTraitInfo objects live in this collection, one per
   /// `pragma omp [begin] declare variant` directive.
   SmallVector<std::unique_ptr<OMPTraitInfo>, 4> OMPTraitInfoVector;
+
+  llvm::DenseMap<GlobalDecl, llvm::StringSet<>> thunksToBeAbbreviated;
 };
 
 /// Insertion operator for diagnostics.
diff --git a/clang/include/clang/AST/GlobalDecl.h b/clang/include/clang/AST/GlobalDecl.h
index 88abba28c991d..386693cabb1fb 100644
--- a/clang/include/clang/AST/GlobalDecl.h
+++ b/clang/include/clang/AST/GlobalDecl.h
@@ -145,6 +145,10 @@ class GlobalDecl {
            LHS.MultiVersionIndex == RHS.MultiVersionIndex;
   }
 
+  bool operator!=(const GlobalDecl &Other) const {
+    return !(*this == Other);
+  }
+
   void *getAsOpaquePtr() const { return Value.getOpaqueValue(); }
 
   explicit operator bool() const { return getAsOpaquePtr(); }
diff --git a/clang/include/clang/AST/Mangle.h b/clang/include/clang/AST/Mangle.h
index e586b0cec43df..d83d56cf42c72 100644
--- a/clang/include/clang/AST/Mangle.h
+++ b/clang/include/clang/AST/Mangle.h
@@ -130,15 +130,15 @@ class MangleContext {
   // FIXME: consider replacing raw_ostream & with something like SmallString &.
   void mangleName(GlobalDecl GD, raw_ostream &);
   virtual void mangleCXXName(GlobalDecl GD, raw_ostream &) = 0;
-  virtual void mangleThunk(const CXXMethodDecl *MD,
-                          const ThunkInfo &Thunk,
-                          raw_ostream &) = 0;
+  virtual void mangleThunk(const CXXMethodDecl *MD, const ThunkInfo &Thunk,
+                           bool elideOverrideInfo, raw_ostream &) = 0;
   virtual void mangleCXXDtorThunk(const CXXDestructorDecl *DD, CXXDtorType Type,
-                                  const ThisAdjustment &ThisAdjustment,
-                                  raw_ostream &) = 0;
+                                  const ThunkInfo &Thunk,
+                                  bool elideOverrideInfo, raw_ostream &) = 0;
   virtual void mangleReferenceTemporary(const VarDecl *D,
                                         unsigned ManglingNumber,
                                         raw_ostream &) = 0;
+  virtual void mangleCXXVTable(const CXXRecordDecl *RD, raw_ostream &) = 0;
   virtual void mangleCXXRTTI(QualType T, raw_ostream &) = 0;
   virtual void mangleCXXRTTIName(QualType T, raw_ostream &,
                                  bool NormalizeIntegers = false) = 0;
@@ -192,7 +192,6 @@ class ItaniumMangleContext : public MangleContext {
                                 bool IsAux = false)
       : MangleContext(C, D, MK_Itanium, IsAux) {}
 
-  virtual void mangleCXXVTable(const CXXRecordDecl *RD, raw_ostream &) = 0;
   virtual void mangleCXXVTT(const CXXRecordDecl *RD, raw_ostream &) = 0;
   virtual void mangleCXXCtorVTable(const CXXRecordDecl *RD, int64_t Offset,
                                    const CXXRecordDecl *Type,
diff --git a/clang/include/clang/AST/VTableBuilder.h b/clang/include/clang/AST/VTableBuilder.h
index fbf6c041a1ec1..a5de41dbc22f1 100644
--- a/clang/include/clang/AST/VTableBuilder.h
+++ b/clang/include/clang/AST/VTableBuilder.h
@@ -361,6 +361,10 @@ class VTableContextBase {
 };
 
 class ItaniumVTableContext : public VTableContextBase {
+public:
+  typedef llvm::DenseMap<const CXXMethodDecl *, const CXXMethodDecl *>
+      OriginalMethodMapTy;
+
 private:
 
   /// Contains the index (relative to the vtable address point)
@@ -384,6 +388,10 @@ class ItaniumVTableContext : public VTableContextBase {
     VirtualBaseClassOffsetOffsetsMapTy;
   VirtualBaseClassOffsetOffsetsMapTy VirtualBaseClassOffsetOffsets;
 
+  /// Map from a virtual method to the nearest method in the primary base class
+  /// chain that it overrides.
+  OriginalMethodMapTy OriginalMethodMap;
+
   void computeVTableRelatedInformation(const CXXRecordDecl *RD) override;
 
 public:
@@ -425,6 +433,27 @@ class ItaniumVTableContext : public VTableContextBase {
   CharUnits getVirtualBaseOffsetOffset(const CXXRecordDecl *RD,
                                        const CXXRecordDecl *VBase);
 
+  /// Return the method that added the v-table slot that will be used to call
+  /// the given method.
+  ///
+  /// In the Itanium ABI, where overrides always cause methods to be added to
+  /// the primary v-table if they're not already there, this will be the first
+  /// declaration in the primary base class chain for which the return type
+  /// adjustment is trivial.
+  GlobalDecl findOriginalMethod(GlobalDecl GD);
+
+  const CXXMethodDecl *findOriginalMethodInMap(const CXXMethodDecl *MD) const;
+
+  void setOriginalMethod(const CXXMethodDecl *Key, const CXXMethodDecl *Val) {
+    OriginalMethodMap[Key] = Val;
+  }
+
+  /// This method is reserved for the implementation and shouldn't be used
+  /// directly.
+  const OriginalMethodMapTy &getOriginalMethodMap() {
+    return OriginalMethodMap;
+  }
+
   static bool classof(const VTableContextBase *VT) {
     return !VT->isMicrosoft();
   }
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index ef9df1e9d8b4a..191afdc147f40 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -680,6 +680,10 @@ class Attr {
   bit PragmaAttributeSupport;
   // Set to true if this attribute accepts parameter pack expansion expressions.
   bit AcceptsExprPack = 0;
+  // To support multiple enum parameters to an attribute without breaking
+  // our existing general parsing we need to have a separate flag that
+  // opts an attribute into strict parsing of attribute parameters
+  bit StrictEnumParameters = 0;
   // Lists language options, one of which is required to be true for the
   // attribute to be applicable. If empty, no language options are required.
   list<LangOpt> LangOpts = [];
@@ -4546,6 +4550,31 @@ def NoRandomizeLayout : InheritableAttr {
 }
 def : MutualExclusions<[RandomizeLayout, NoRandomizeLayout]>;
 
+def VTablePointerAuthentication : InheritableAttr {
+  let Spellings = [Clang<"ptrauth_vtable_pointer">];
+  let Subjects = SubjectList<[CXXRecord]>;
+  let Documentation = [Undocumented];
+  let StrictEnumParameters = 1;
+  let Args = [EnumArgument<"Key", "VPtrAuthKeyType", /*is_string=*/ true,
+                ["default_key", "no_authentication", "process_dependent",
+                 "process_independent"],
+                ["DefaultKey", "NoKey", "ProcessDependent",
+                 "ProcessIndependent"]>,
+              EnumArgument<"AddressDiscrimination", "AddressDiscriminationMode",
+                /*is_string=*/ true,
+                ["default_address_discrimination", "no_address_discrimination",
+                 "address_discrimination"],
+                ["DefaultAddressDiscrimination", "NoAddressDiscrimination",
+                 "AddressDiscrimination"]>,
+              EnumArgument<"ExtraDiscrimination", "ExtraDiscrimination",
+                /*is_string=*/ true,
+                ["default_extra_discrimination", "no_extra_discrimination",
+                 "type_discrimination", "custom_discrimination"],
+                ["DefaultExtraDiscrimination", "NoExtraDiscrimination",
+                 "TypeDiscrimination", "CustomDiscrimination"]>,
+              IntArgument<"CustomDiscriminationValue", 1>];
+}
+
 def FunctionReturnThunks : InheritableAttr,
     TargetSpecificAttr<TargetAnyX86> {
   let Spellings = [GCC<"function_return">];
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 753e775ce0968..88b8fd344648b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -938,6 +938,13 @@ def warn_ptrauth_auth_null_pointer :
 def err_ptrauth_string_not_literal : Error<
   "argument must be a string literal%select{| of char type}0">;
 
+def note_ptrauth_virtual_function_pointer_incomplete_arg_ret :
+  Note<"cannot take an address of a virtual member function if its return or "
+       "argument types are incomplete">;
+def note_ptrauth_virtual_function_incomplete_arg_ret_type :
+  Note<"%0 is incomplete">;
+
+
 /// main()
 // static main() is not an error in C, just in C++.
 def warn_static_main : Warning<"'main' should not be declared static">,
@@ -12175,6 +12182,30 @@ def warn_cuda_maxclusterrank_sm_90 : Warning<
   "maxclusterrank requires sm_90 or higher, CUDA arch provided: %0, ignoring "
   "%1 attribute">, InGroup<IgnoredAttributes>;
 
+// VTable pointer authentication errors
+def err_non_polymorphic_vtable_pointer_auth : Error<
+  "cannot set vtable pointer authentication on monomorphic type %0">;
+def err_incomplete_type_vtable_pointer_auth : Error<
+  "cannot set vtable pointer authentication on an incomplete type %0">;
+def err_non_top_level_vtable_pointer_auth : Error<
+  "cannot set vtable pointer authentication on %0 which is a subclass of polymorphic type %1">;
+def err_duplicated_vtable_pointer_auth : Error<
+  "multiple vtable pointer authentication policies on %0">;
+def err_invalid_authentication_key : Error<
+  "invalid authentication key %0">;
+def err_invalid_address_discrimination : Error<
+  "invalid address discrimination mode %0">;
+def err_invalid_extra_discrimination : Error<
+  "invalid extra discrimination selection %0">;
+def err_invalid_custom_discrimination : Error<
+  "invalid custom discrimination">;
+def err_missing_custom_discrimination : Error<
+  "missing custom discrimination">;
+def err_no_default_vtable_pointer_auth : Error<
+  "cannot specify a default vtable pointer authentication "
+  "%select{key|address discrimination mode|discriminator}0 with no default set"
+>;
+
 def err_bit_int_bad_size : Error<"%select{signed|unsigned}0 _BitInt must "
                                  "have a bit size of at least %select{2|1}0">;
 def err_bit_int_max_size : Error<"%select{signed|unsigned}0 _BitInt of bit "
diff --git a/clang/include/clang/Basic/PointerAuthOptions.h b/clang/include/clang/Basic/PointerAuthOptions.h
index 32b179e3f9460..d4d794a6fabe0 100644
--- a/clang/include/clang/Basic/PointerAuthOptions.h
+++ b/clang/include/clang/Basic/PointerAuthOptions.h
@@ -49,6 +49,12 @@ class PointerAuthSchema {
     /// No additional discrimination.
     None,
 
+    /// Include a hash of the entity's type.
+    Type,
+
+    /// Include a hash of the entity's identity.
+    Decl,
+
     /// Discriminate using a constant value.
     Constant,
   };
@@ -152,6 +158,25 @@ class PointerAuthSchema {
 struct PointerAuthOptions {
   /// The ABI for C function pointers.
   PointerAuthSchema FunctionPointers;
+
+  /// The ABI for C++ virtual table pointers (the pointer to the table
+  /// itself) as installed in an actual class instance.
+  PointerAuthSchema CXXVTablePointers;
+
+  /// TypeInfo has external ABI requirements and is emitted without
+  /// actually having parsed the libcxx definition, so we can't simply
+  /// perform a look up. The settings for this should match the exact
+  /// specification in type_info.h
+  PointerAuthSchema CXXTypeInfoVTablePointer;
+
+  /// The ABI for C++ virtual table pointers as installed in a VTT.
+  PointerAuthSchema CXXVTTVTablePointers;
+
+  /// The ABI for most C++ virtual function pointers, i.e. v-table entries.
+  PointerAuthSchema CXXVirtualFunctionPointers;
+
+  /// The ABI for variadic C++ virtual function pointers.
+  PointerAuthSchema CXXVirtualVariadicFunctionPointers;
 };
 
 } // end namespace clang
diff --git a/clang/include/clang/Basic/Thunk.h b/clang/include/clang/Basic/Thunk.h
index 0247e279408f0..4dccebf687585 100644
--- a/clang/include/clang/Basic/Thunk.h
+++ b/clang/include/clang/Basic/Thunk.h
@@ -162,20 +162,27 @@ struct ThunkInfo {
 
   /// Holds a pointer to the overridden method this thunk is for,
   /// if needed by the ABI to distinguish different thunks with equal
-  /// adjustments. Otherwise, null.
+  /// adjustments.
+  /// In the Itanium ABI, this field can hold the method that created the
+  /// vtable entry for this thunk.
+  /// Otherwise, null.
   /// CAUTION: In the unlikely event you need to sort ThunkInfos, consider using
   /// an ABI-specific comparator.
   const CXXMethodDecl *Method;
+  const Type *ThisType { nullptr };
 
   ThunkInfo() : Method(nullptr) {}
 
   ThunkInfo(const ThisAdjustment &This, const ReturnAdjustment &Return,
+            const Type *thisType,
             const CXXMethodDecl *Method = nullptr)
-      : This(This), Return(Return), Method(Method) {}
+      : This(This), Return(Return), Method(Method),
+        ThisType(thisType) {}
 
   friend bool operator==(const ThunkInfo &LHS, const ThunkInfo &RHS) {
     return LHS.This == RHS.This && LHS.Return == RHS.Return &&
-           LHS.Method == RHS.Method;
+           LHS.Method == RHS.Method &&
+           LHS.ThisType == RHS.ThisType;
   }
 
   bool isEmpty() const {
diff --git a/clang/include/clang/CodeGen/CodeGenABITypes.h b/clang/include/clang/CodeGen/CodeGenABITypes.h
index 8c62d8597ecbe..0d0c152c6fea0 100644
--- a/clang/include/clang/CodeGen/CodeGenABITypes.h
+++ b/clang/include/clang/CodeGen/CodeGenABITypes.h
@@ -42,6 +42,7 @@ class CXXConstructorDecl;
 class CXXDestructorDecl;
 class CXXRecordDecl;
 class CXXMethodDecl;
+class GlobalDecl;
 class ObjCMethodDecl;
 class ObjCProtocolDecl;
 
@@ -104,6 +105,9 @@ llvm::Type *convertTypeForMemory(CodeGenModule &CGM, QualType T);
 unsigned getLLVMFieldNumber(CodeGenModule &CGM,
                             const RecordDecl *RD, const FieldDecl *FD);
 
+/// Return a declaration discriminator for the given global decl.
+uint16_t getPointerAuthDeclDiscriminator(CodeGenModule &CGM, GlobalDecl GD);
+
 /// Return a signed constant pointer.
 llvm::Constant *getConstantSignedPointer(CodeGenModule &CGM,
                                          llvm::Constant *pointer,
diff --git a/clang/include/clang/CodeGen/ConstantInitBuilder.h b/clang/include/clang/CodeGen/ConstantInitBuilder.h
index 498acfd380131..f6d8e15ff2131 100644
--- a/clang/include/clang/CodeGen/ConstantInitBuilder.h
+++ b/clang/include/clang/CodeGen/ConstantInitBuilder.h
@@ -25,8 +25,11 @@
 #include <vector>
 
 namespace clang {
-namespace CodeGen {
+class GlobalDecl;
+class PointerAuthSchema;
+class QualType;
 
+namespace CodeGen {
 class CodeGenModule;
 
 /// A convenience builder class for complex constant initializers,
@@ -199,6 +202,17 @@ class ConstantAggregateBuilderBase {
     add(llvm::ConstantInt::get(intTy, value, isSigned));
   }
 
+  /// Add a signed pointer using the given pointer authentication schema.
+  void addSignedPointer(llvm::Constant *pointer,
+                        const PointerAuthSchema &schema, GlobalDecl calleeDecl,
+                        QualType calleeType);
+
+  /// Add a signed pointer using the given pointer authentication schema.
+  void addSignedPointer(llvm::Constant *pointer,
+                        unsigned key,
+                        bool useAddressDiscrimination,
+                        llvm::Constant *otherDiscriminator);
+
   /// Add a null pointer of a specific type.
   void addNullPointer(llvm::PointerType *ptrTy) {
     add(llvm::ConstantPointerNull::get(ptrTy));
diff --git a/clang/include/clang/InstallAPI/Visitor.h b/clang/include/clang/InstallAPI/Visitor.h
index 9ac948ded3e33..3680ee566ca87 100644
--- a/clang/include/clang/InstallAPI/Visitor.h
+++ b/clang/include/clang/InstallAPI/Visitor.h
@@ -60,8 +60,8 @@ class InstallAPIVisitor final : public ASTConsumer,
   std::string getMangledName(const NamedDecl *D) const;
   std::string getBackendMangledName(llvm::Twine Name) const;
   std::string getMangledCXXVTableName(const CXXRecordDecl *D) const;
-  std::string getMangledCXXThunk(const GlobalDecl &D,
-                                 const ThunkInfo &Thunk) const;
+  std::string getMangledCXXThunk(const GlobalDecl &D, const ThunkInfo &Thunk,
+                                 bool ElideOverrideInfo) const;
   std::string getMangledCXXRTTI(const CXXRecordDecl *D) const;
   std::string getMangledCXXRTTIName(const CXXRecordDecl *D) const;
   std::string getMangledCtorDtor(const CXXMethodDecl *D, int Type) const;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ec083f7cc09b7..dd65662e77860 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -4401,6 +4401,10 @@ class Sema final : public SemaBase {
   /// conditions that are needed for the attribute to have an effect.
   void checkIllFormedTrivialABIStruct(CXXRecordDecl &RD);
 
+  /// Check that VTable Pointer authentication is only being set on the first
+  /// first instantiation of the vtable
+  void checkIncorrectVTablePointerAuthenticationAttribute(CXXRecordDecl &RD);
+
   void ActOnFinishCXXMemberSpecification(Scope *S, SourceLocation RLoc,
                                          Decl *TagDecl, SourceLocation LBrac,
                                          SourceLocation RBrac,
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index a2398fef623ea..2e230ce556459 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -86,6 +86,7 @@
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/MD5.h"
 #include "llvm/Support/MathExtras.h"
+#include "llvm/Support/SipHash.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/TargetParser/Triple.h"
 #include <algorithm>
@@ -3088,6 +3089,17 @@ QualType ASTContext::removeAddrSpaceQualType(QualType T) const {
     return QualType(TypeNode, Quals.getFastQualifiers());
 }
 
+uint16_t ASTContext::getPointerAuthVTablePointerDiscriminator(
+    const CXXRecordDecl *record) {
+  assert(record->isPolymorphic() &&
+         "Attempted to get vtable pointer discriminator on a monomorphic type");
+  std::unique_ptr<MangleContext> MC(createMangleContext());
+  SmallString<256> Str;
+  llvm::raw_svector_ostream Out(Str);
+  MC->mangleCXXVTable(record, Out);
+  return llvm::getPointerAuthStableSipHash16(Str.c_str());
+}
+
 QualType ASTContext::getObjCGCQualType(QualType T,
                                        Qualifiers::GC GCAttr) const {
   QualType CanT = getCanonicalType(T);
@@ -13812,3 +13824,77 @@ StringRef ASTContext::getCUIDHash() const {
   CUIDHash = llvm::utohexstr(llvm::MD5Hash(LangOpts.CUID), /*LowerCase=*/true);
   return CUIDHash;
 }
+
+const CXXRecordDecl *
+ASTContext::baseForVTableAuthentication(const CXXRecordDecl *thisClass) {
+  assert(thisClass);
+  assert(thisClass->isPolymorphic());
+  const CXXRecordDecl *primaryBase = thisClass;
+  while (1) {
+    assert(primaryBase);
+    assert(primaryBase->isPolymorphic());
+    auto &layout = getASTRecordLayout(primaryBase);
+    auto base = layout.getPrimaryBase();
+    if (!base || base == primaryBase || !base->isPolymorphic())
+      break;
+    primaryBase = base;
+  }
+  return primaryBase;
+}
+
+bool ASTContext::useAbbreviatedThunkName(GlobalDecl virtualMethodDecl,
+                                         StringRef mangledName) {
+  auto method = cast<CXXMethodDecl>(virtualMethodDe...
[truncated]

Copy link
Contributor

@Endilll Endilll left a comment

Choose a reason for hiding this comment

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

Sema.h changes look good.

@asl asl removed the backend:X86 label Jun 3, 2024 — with Graphite App
@@ -1261,6 +1262,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
/// space.
QualType removeAddrSpaceQualType(QualType T) const;

/// Return the "other" type-specific discriminator for the given type.
Copy link
Collaborator

Choose a reason for hiding this comment

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

The comment here looks a bit misleading without knowing what function is doing. Could it be reformulated in some better way?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How would /// Return the "other" discriminator used for the pointer auth schema used for vtable pointers in instances of the requested type sound? it's more explicit about what is being produced

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@asl thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

@ojhunt This looks much better, thanks!

@ojhunt ojhunt force-pushed the users/ojhunt/ptrauth-cpp-vtables branch from 840cc18 to 9a7be2c Compare June 7, 2024 19:54
@ojhunt
Copy link
Contributor Author

ojhunt commented Jun 7, 2024

Had to do a force push to resolve merge conflicts following @ahatanak's PR, so this change now includes CodeGenFunction::EmitPointerAuthAuth etc


ThunkInfo() : Method(nullptr) {}

ThunkInfo(const ThisAdjustment &This, const ReturnAdjustment &Return,
const Type *thisType,
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: consistent naming of parameters

AddMethod(FinalOverriderMD,
ThunkInfo(ThisAdjustmentOffset, ReturnAdjustment,
thisType,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
thisType,
ThisType,

@ahmedbougacha ahmedbougacha force-pushed the users/ahmedbougacha/ptrauth-function-pointers branch from 06529d3 to 99b2bf2 Compare June 20, 2024 19:43
@ahatanak ahatanak deleted the branch llvm:main June 21, 2024 17:20
@ahatanak ahatanak closed this Jun 21, 2024
@ahmedbougacha ahmedbougacha reopened this Jun 21, 2024
@ahmedbougacha ahmedbougacha changed the base branch from users/ahmedbougacha/ptrauth-function-pointers to main June 21, 2024 17:45
@ojhunt ojhunt force-pushed the users/ojhunt/ptrauth-cpp-vtables branch from 6495c25 to 72dff4c Compare June 21, 2024 22:17
ojhunt and others added 9 commits June 24, 2024 16:13
…-tables, and VTTs.

This also implements the ptrauth_vtable_pointer attribute to allow
overriding the default ptrauth schema for vtable pointers.

Co-Authored-By: John McCall <[email protected]>
- update ubsan vptr codegen/tests for hash/mixer change
- adopt renamed siphash function; pass SmallString as StringRef
- bring back removed getConstantSignedPointer declaration
- move shouldSignPointer into CGM
- remove unneeded addSignedPointer variant
- move other addSignedPointer variant back to ConstantInitBuilder
If it wasn't for inttoptr/ptrtoint, this probably could have been
stripPointerCasts anyway.

Given the sole existing user now only produces plain ConstantPtrAuth
values anyway (unlike the previous dance with llvm.ptrauth global),
I don't think this is needed anymore.
@ahmedbougacha ahmedbougacha force-pushed the users/ojhunt/ptrauth-cpp-vtables branch from 72dff4c to 4d2874c Compare June 25, 2024 20:48
@ahmedbougacha ahmedbougacha merged commit 1b8ab2f into llvm:main Jun 27, 2024
3 checks passed
@ahmedbougacha ahmedbougacha deleted the users/ojhunt/ptrauth-cpp-vtables branch June 27, 2024 01:35
@llvm-ci
Copy link
Collaborator

llvm-ci commented Jun 27, 2024

LLVM Buildbot has detected a new failure on builder llvm-clang-x86_64-darwin running on doug-worker-3 while building clang at step 6 "test-build-unified-tree-check-all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/23/builds/407

Here is the relevant piece of the build log for the reference:

Step 6 (test-build-unified-tree-check-all) failure: 1200 seconds without output running [b'ninja', b'check-all'], attempting to kill
******************** TEST 'lld :: COFF/subsystem-inference.test' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
RUN: at line 1: sed -e s/ENTRYNAME/main/ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/llvm-project/lld/test/COFF/subsystem-inference.test | /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/yaml2obj > /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.obj
+ sed -e s/ENTRYNAME/main/ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/llvm-project/lld/test/COFF/subsystem-inference.test
+ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/yaml2obj
RUN: at line 2: /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/lld-link /out:/Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.exe /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.obj
+ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/lld-link /out:/Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.exe /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.obj
RUN: at line 3: /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/llvm-readobj --file-headers /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.exe | /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/FileCheck -check-prefix=MAIN /Volumes/RAMDisk/buildbot-root/x86_64-darwin/llvm-project/lld/test/COFF/subsystem-inference.test
+ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/llvm-readobj --file-headers /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.exe
+ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/FileCheck -check-prefix=MAIN /Volumes/RAMDisk/buildbot-root/x86_64-darwin/llvm-project/lld/test/COFF/subsystem-inference.test
RUN: at line 4: /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/lld-link /out:/Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.exe /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.obj /subsystem:default,6.0
+ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/lld-link /out:/Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.exe /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.obj /subsystem:default,6.0
RUN: at line 5: /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/llvm-readobj --file-headers /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.exe | /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/FileCheck -check-prefix=MAIN /Volumes/RAMDisk/buildbot-root/x86_64-darwin/llvm-project/lld/test/COFF/subsystem-inference.test
+ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/llvm-readobj --file-headers /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.exe
+ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/FileCheck -check-prefix=MAIN /Volumes/RAMDisk/buildbot-root/x86_64-darwin/llvm-project/lld/test/COFF/subsystem-inference.test
RUN: at line 7: sed s/ENTRYNAME/wmain/ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/llvm-project/lld/test/COFF/subsystem-inference.test | /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/yaml2obj > /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.obj
+ sed s/ENTRYNAME/wmain/ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/llvm-project/lld/test/COFF/subsystem-inference.test
+ /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/bin/yaml2obj
/Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.script: line 6: /Volumes/RAMDisk/buildbot-root/x86_64-darwin/build/tools/lld/test/COFF/Output/subsystem-inference.test.tmp.obj: No space left on device

--

********************


lravenclaw pushed a commit to lravenclaw/llvm-project that referenced this pull request Jul 3, 2024
…v-tables, and VTTs (llvm#94056)

Virtual function pointer entries in v-tables are signed with address
discrimination in addition to declaration-based discrimination, where an
integer discriminator the string hash (see
`ptrauth_string_discriminator`) of the mangled name of the overridden
method. This notably provides diversity based on the full signature of
the overridden method, including the method name and parameter types.
This patch introduces ItaniumVTableContext logic to find the original
declaration of the overridden method.
On AArch64, these pointers are signed using the `IA` key (the
process-independent code key.)

V-table pointers can be signed with either no discrimination, or a
similar scheme using address and decl-based discrimination. In this
case, the integer discriminator is the string hash of the mangled
v-table identifier of the class that originally introduced the vtable
pointer.
On AArch64, these pointers are signed using the `DA` key (the
process-independent data key.)

Not using discrimination allows attackers to simply copy valid v-table
pointers from one object to another. However, using a uniform
discriminator of 0 does have positive performance and code-size
implications on AArch64, and diversity for the most important v-table
access pattern (virtual dispatch) is already better assured by the
signing schemas used on the virtual functions. It is also known that
some code in practice copies objects containing v-tables with `memcpy`,
and while this is not permitted formally, it is something that may be
invasive to eliminate.

This is controlled by:
```
  -fptrauth-vtable-pointer-type-discrimination
  -fptrauth-vtable-pointer-address-discrimination
```

In addition, this provides fine-grained controls in the
ptrauth_vtable_pointer attribute, which allows overriding the default
ptrauth schema for vtable pointers on a given class hierarchy, e.g.:
```
  [[clang::ptrauth_vtable_pointer(no_authentication, no_address_discrimination, 
                                  no_extra_discrimination)]]
  [[clang::ptrauth_vtable_pointer(default_key, default_address_discrimination,
                                  custom_discrimination, 0xf00d)]]
```

The override is then mangled as a parametrized vendor extension:
```
"__vtptrauth" I
 <key>
 <addressDiscriminated>
 <extraDiscriminator>
E
```

To support this attribute, this patch adds a small extension to the
attribute-emitter tablegen backend.

Note that there are known areas where signing is either missing
altogether or can be strengthened. Some will be addressed in later
changes (e.g., member function pointers, some RTTI).
`dynamic_cast` in particular is handled by emitting an artificial
v-table pointer load (in a way that always authenticates it) before the
runtime call itself, as the runtime doesn't have enough information
today to properly authenticate it. Instead, the runtime is currently
expected to strip the v-table pointer.

---------

Co-authored-by: John McCall <[email protected]>
Co-authored-by: Ahmed Bougacha <[email protected]>
AlexisPerry pushed a commit to llvm-project-tlp/llvm-project that referenced this pull request Jul 9, 2024
…v-tables, and VTTs (llvm#94056)

Virtual function pointer entries in v-tables are signed with address
discrimination in addition to declaration-based discrimination, where an
integer discriminator the string hash (see
`ptrauth_string_discriminator`) of the mangled name of the overridden
method. This notably provides diversity based on the full signature of
the overridden method, including the method name and parameter types.
This patch introduces ItaniumVTableContext logic to find the original
declaration of the overridden method.
On AArch64, these pointers are signed using the `IA` key (the
process-independent code key.)

V-table pointers can be signed with either no discrimination, or a
similar scheme using address and decl-based discrimination. In this
case, the integer discriminator is the string hash of the mangled
v-table identifier of the class that originally introduced the vtable
pointer.
On AArch64, these pointers are signed using the `DA` key (the
process-independent data key.)

Not using discrimination allows attackers to simply copy valid v-table
pointers from one object to another. However, using a uniform
discriminator of 0 does have positive performance and code-size
implications on AArch64, and diversity for the most important v-table
access pattern (virtual dispatch) is already better assured by the
signing schemas used on the virtual functions. It is also known that
some code in practice copies objects containing v-tables with `memcpy`,
and while this is not permitted formally, it is something that may be
invasive to eliminate.

This is controlled by:
```
  -fptrauth-vtable-pointer-type-discrimination
  -fptrauth-vtable-pointer-address-discrimination
```

In addition, this provides fine-grained controls in the
ptrauth_vtable_pointer attribute, which allows overriding the default
ptrauth schema for vtable pointers on a given class hierarchy, e.g.:
```
  [[clang::ptrauth_vtable_pointer(no_authentication, no_address_discrimination, 
                                  no_extra_discrimination)]]
  [[clang::ptrauth_vtable_pointer(default_key, default_address_discrimination,
                                  custom_discrimination, 0xf00d)]]
```

The override is then mangled as a parametrized vendor extension:
```
"__vtptrauth" I
 <key>
 <addressDiscriminated>
 <extraDiscriminator>
E
```

To support this attribute, this patch adds a small extension to the
attribute-emitter tablegen backend.

Note that there are known areas where signing is either missing
altogether or can be strengthened. Some will be addressed in later
changes (e.g., member function pointers, some RTTI).
`dynamic_cast` in particular is handled by emitting an artificial
v-table pointer load (in a way that always authenticates it) before the
runtime call itself, as the runtime doesn't have enough information
today to properly authenticate it. Instead, the runtime is currently
expected to strip the v-table pointer.

---------

Co-authored-by: John McCall <[email protected]>
Co-authored-by: Ahmed Bougacha <[email protected]>
kovdan01 added a commit that referenced this pull request Jul 19, 2024
Enhance tests introduced in #94056, #96992, #98276 and #98847 by adding
RUN and CHECK lines against linux triples.
yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
Summary:
Enhance tests introduced in #94056, #96992, #98276 and #98847 by adding
RUN and CHECK lines against linux triples.

Test Plan: 

Reviewers: 

Subscribers: 

Tasks: 

Tags: 


Differential Revision: https://phabricator.intern.facebook.com/D60251378
@AaronBallman
Copy link
Collaborator

It was observed in #99993 that this PR introduced tablegen code that appears to be unnecessary (attributeHasStrictIdentifierArgAtIndex()), and that makes me worry that these changes are not correct (certainly they lack test coverage).

What was the intent behind that code? Should it be removed or did we discover a bug with this PR?

CC @ojhunt @ahmedbougacha @asl

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:AArch64 backend:X86 clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:headers Headers provided by Clang, e.g. for intrinsics clang Clang issues not falling into any other category
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

8 participants