| # Pointer Authentication | |
| ## Introduction | |
| Pointer authentication is a technology which offers strong probabilistic | |
| protection against exploiting a broad class of memory bugs to take control of | |
| program execution. When adopted consistently in a language ABI, it provides | |
| a form of relatively fine-grained control flow integrity (CFI) check that | |
| resists both return-oriented programming (ROP) and jump-oriented programming | |
| (JOP) attacks. | |
| While pointer authentication can be implemented purely in software, direct | |
| hardware support (e.g. as provided by Armv8.3 PAuth) can dramatically improve | |
| performance and code size. Similarly, while pointer authentication | |
| can be implemented on any architecture, taking advantage of the (typically) | |
| excess addressing range of a target with 64-bit pointers minimizes the impact | |
| on memory performance and can allow interoperation with existing code (by | |
| disabling pointer authentication dynamically). This document will generally | |
| attempt to present the pointer authentication feature independent of any | |
| hardware implementation or ABI. Considerations that are | |
| implementation-specific are clearly identified throughout. | |
| Note that there are several different terms in use: | |
| - **Pointer authentication** is a target-independent language technology. | |
| - **PAuth** (sometimes referred to as **PAC**, for Pointer Authentication | |
| Codes) is an AArch64 architecture extension that provides hardware support | |
| for pointer authentication. Additional extensions either modify some of the | |
| PAuth instruction behavior (notably FPAC), or provide new instruction | |
| variants (PAuth_LR). | |
| - **Armv8.3** is an AArch64 architecture revision that makes PAuth mandatory. | |
| - **arm64e** is a specific ABI (not yet fully stable) for implementing pointer | |
| authentication using PAuth on certain Apple operating systems. | |
| This document serves four purposes: | |
| - It describes the basic ideas of pointer authentication. | |
| - It documents several language extensions that are useful on targets using | |
| pointer authentication. | |
| - It presents a theory of operation for the security mitigation, describing the | |
| basic requirements for correctness, various weaknesses in the mechanism, and | |
| ways in which programmers can strengthen its protections (including | |
| recommendations for language implementors). | |
| - It documents the stable ABI of the C, C++, and Objective-C languages on arm64e | |
| platforms. | |
| ## Basic concepts | |
| The simple address of an object or function is a **raw pointer**. A raw | |
| pointer can be **signed** to produce a **signed pointer**. A signed pointer | |
| can be then **authenticated** in order to verify that it was **validly signed** | |
| and extract the original raw pointer. These terms reflect the most likely | |
| implementation technique: computing and storing a cryptographic signature along | |
| with the pointer. | |
| An **abstract signing key** is a name which refers to a secret key which is | |
| used to sign and authenticate pointers. The concrete key value for a | |
| particular name is consistent throughout a process. | |
| A **discriminator** is an arbitrary value used to **diversify** signed pointers | |
| so that one validly-signed pointer cannot simply be copied over another. | |
| A discriminator is simply opaque data of some implementation-defined size that | |
| is included in the signature as a salt (see [Discriminators](#discriminators) for details.) | |
| Nearly all aspects of pointer authentication use just these two primary | |
| operations: | |
| - `sign(raw_pointer, key, discriminator)` produces a signed pointer given | |
| a raw pointer, an abstract signing key, and a discriminator. | |
| - `auth(signed_pointer, key, discriminator)` produces a raw pointer given | |
| a signed pointer, an abstract signing key, and a discriminator. | |
| `auth(sign(raw_pointer, key, discriminator), key, discriminator)` must | |
| succeed and produce `raw_pointer`. `auth` applied to a value that was | |
| ultimately produced in any other way is expected to fail, which halts the | |
| program either: | |
| - immediately, on implementations that enforce `auth` success (e.g., when | |
| using compiler-generated `auth` failure checks, or Armv8.3 with the FPAC | |
| extension), or | |
| - when the resulting pointer value is used, on implementations that don't. | |
| However, regardless of the implementation's handling of `auth` failures, it | |
| is permitted for `auth` to fail to detect that a signed pointer was not | |
| produced in this way, in which case it may return anything; this is what makes | |
| pointer authentication a probabilistic mitigation rather than a perfect one. | |
| There are two secondary operations which are required only to implement certain | |
| intrinsics in `<ptrauth.h>`: | |
| - `strip(signed_pointer, key)` produces a raw pointer given a signed pointer | |
| and a key without verifying its validity, unlike `auth`. This is useful | |
| for certain kinds of tooling, such as crash backtraces; it should generally | |
| not be used in the basic language ABI except in very careful ways. | |
| - `sign_generic(value)` produces a cryptographic signature for arbitrary | |
| data, not necessarily a pointer. This is useful for efficiently verifying | |
| that non-pointer data has not been tampered with. | |
| Whenever any of these operations is called for, the key value must be known | |
| statically. This is because the layout of a signed pointer may vary according | |
| to the signing key. (For example, in Armv8.3, the layout of a signed pointer | |
| depends on whether Top Byte Ignore (TBI) is enabled, which can be set | |
| independently for I and D keys.) | |
| :::{admonition} Note for API designers and language implementors | |
| These are the *primitive* operations of pointer authentication, provided for | |
| clarity of description. They are not suitable either as high-level | |
| interfaces or as primitives in a compiler IR because they expose raw | |
| pointers. Raw pointers require special attention in the language | |
| implementation to avoid the accidental creation of exploitable code | |
| sequences; see the section on [Attackable code sequences](#attackable-code-sequences). | |
| ::: | |
| The following details are all implementation-defined: | |
| - the nature of a signed pointer | |
| - the size of a discriminator | |
| - the number and nature of the signing keys | |
| - the implementation of the `sign`, `auth`, `strip`, and `sign_generic` | |
| operations | |
| While the use of the terms "sign" and "signed pointer" suggest the use of | |
| a cryptographic signature, other implementations may be possible. See | |
| [Alternative implementations](#alternative-implementations) for an exploration of implementation options. | |
| :::{admonition} Implementation example: Armv8.3 | |
| Readers may find it helpful to know how these terms map to Armv8.3 PAuth: | |
| - A signed pointer is a pointer with a signature stored in the | |
| otherwise-unused high bits. The kernel configures the address width based | |
| on the system's addressing needs, and enables TBI for I or D keys as | |
| needed. The bits above the address bits and below the TBI bits (if | |
| enabled) are unused. The signature width then depends on this addressing | |
| configuration. | |
| - A discriminator is a 64-bit integer. Constant discriminators are 16-bit | |
| integers. Blending a constant discriminator into an address consists of | |
| replacing the top 16 bits of the pointer containing the address with the | |
| constant. Pointers used for blending purposes should only have address | |
| bits, since higher bits will be at least partially overwritten with the | |
| constant discriminator. | |
| - There are five 128-bit signing-key registers, each of which can only be | |
| directly read or set by privileged code. Of these, four are used for | |
| signing pointers, and the fifth is used only for `sign_generic`. The key | |
| data is simply a pepper added to the hash, not an encryption key, and so | |
| can be initialized using random data. | |
| - `sign` computes a cryptographic hash of the pointer, discriminator, and | |
| signing key, and stores it in the high bits as the signature. `auth` | |
| removes the signature, computes the same hash, and compares the result with | |
| the stored signature. `strip` removes the signature without | |
| authenticating it. The `aut` instructions in the baseline Armv8.3 PAuth | |
| feature do not guarantee to trap on authentication failure; instead, they | |
| simply corrupt the pointer so that later uses will likely trap. Unless the | |
| "later use" follows immediately and cannot be recovered from (e.g. with a | |
| signal handler), this does not provide adequate protection against | |
| {ref}`authentication oracles <authentication-oracles>`, so implementations must emit additional | |
| instructions to force an immediate trap. This is unnecessary if the | |
| processor provides the optional `FPAC` extension, which guarantees an | |
| immediate trap. | |
| - `sign_generic` corresponds to the `pacga` instruction, which takes two | |
| 64-bit values and produces a 64-bit cryptographic hash. Implementations of | |
| this instruction are not required to produce meaningful data in all bits of | |
| the result. | |
| ::: | |
| ### Discriminators | |
| A discriminator is arbitrary extra data which alters the signature calculated | |
| for a pointer. When two pointers are signed differently --- either with | |
| different keys or with different discriminators --- an attacker cannot simply | |
| replace one pointer with the other. | |
| To use standard cryptographic terminology, a discriminator acts as a | |
| [salt](<https://en.wikipedia.org/wiki/Salt_(cryptography)>) in the signing of a | |
| pointer, and the key data acts as a | |
| [pepper](<https://en.wikipedia.org/wiki/Pepper_(cryptography)>). That is, | |
| both the discriminator and key data are ultimately just added as inputs to the | |
| signing algorithm along with the pointer, but they serve significantly | |
| different roles. The key data is a common secret added to every signature, | |
| whereas the discriminator is a value that can be derived from | |
| the context in which a specific pointer is signed. However, unlike a password | |
| salt, it's important that discriminators be *independently* derived from the | |
| circumstances of the signing; they should never simply be stored alongside | |
| a pointer. Discriminators are then re-derived in authentication operations. | |
| The intrinsic interface in `<ptrauth.h>` allows an arbitrary discriminator | |
| value to be provided, but can only be used when running normal code. The | |
| discriminators used by language ABIs must be restricted to make it feasible for | |
| the loader to sign pointers stored in global memory without needing excessive | |
| amounts of metadata. Under these restrictions, a discriminator may consist of | |
| either or both of the following: | |
| - The address at which the pointer is stored in memory. A pointer signed with | |
| a discriminator which incorporates its storage address is said to have | |
| **address diversity**. In general, using address diversity means that | |
| a pointer cannot be reliably copied by an attacker to or from a different | |
| memory location. However, an attacker may still be able to attack a larger | |
| call sequence if they can alter the address through which the pointer is | |
| accessed. Furthermore, some situations cannot use address diversity because | |
| of language or other restrictions. | |
| - A constant integer, called a **constant discriminator**. A pointer signed | |
| with a non-zero constant discriminator is said to have **constant | |
| diversity**. If the discriminator is specific to a single declaration, it is | |
| said to have **declaration diversity**; if the discriminator is specific to | |
| a type of value, it is said to have **type diversity**. For example, C++ | |
| v-tables on arm64e sign their component functions using a hash of their | |
| method names and signatures, which provides declaration diversity; similarly, | |
| C++ member function pointers sign their invocation functions using a hash of | |
| the member pointer type, which provides type diversity. | |
| The implementation may need to restrict constant discriminators to be | |
| significantly smaller than the full size of a discriminator. For example, on | |
| arm64e, constant discriminators are only 16-bit values. This is believed to | |
| not significantly weaken the mitigation, since collisions remain uncommon. | |
| The algorithm for blending a constant discriminator with a storage address is | |
| implementation-defined. | |
| (signing-schemas)= | |
| ### Signing schemas | |
| Correct use of pointer authentication requires the signing code and the | |
| authenticating code to agree about the **signing schema** for the pointer: | |
| - the abstract signing key with which the pointer should be signed and | |
| - an algorithm for computing the discriminator. | |
| As described in the section above on [Discriminators](#discriminators), in most situations, the | |
| discriminator is produced by taking a constant discriminator and optionally | |
| blending it with the storage address of the pointer. In these situations, the | |
| signing schema breaks down even more simply: | |
| - the abstract signing key, | |
| - a constant discriminator, and | |
| - whether to use address diversity. | |
| It is important that the signing schema be independently derived at all signing | |
| and authentication sites. Preferably, the schema should be hard-coded | |
| everywhere it is needed, but at the very least, it must not be derived by | |
| inspecting information stored along with the pointer. See the section on | |
| [Attacks on pointer authentication](#attacks-on-pointer-authentication) for more information. | |
| ## Language features | |
| There are three levels of the pointer authentication language feature: | |
| - The language implementation automatically signs and authenticates function | |
| pointers (and certain data pointers) across a variety of standard situations, | |
| including return addresses, function pointers, and C++ virtual functions. The | |
| intent is for all pointers to code in program memory to be signed in some way | |
| and for all branches to code in program text to authenticate those | |
| signatures. In addition to the code pointers themselves, we also use pointer | |
| authentication to protect data values that directly or indirectly influence | |
| control flow or program integrity, or can provide attackers with some other | |
| powerful program compromise. | |
| - The language also provides extensions to override the default rules used by | |
| the language implementation. For example, the `__ptrauth` type qualifier | |
| can be used to change how pointers or pointer sized integers are signed when | |
| they are stored in a particular variable or field; this provides much stronger | |
| protection than is guaranteed by the default rules for C function and data | |
| pointers. | |
| - Finally, the language provides the `<ptrauth.h>` intrinsic interface for | |
| manually signing and authenticating pointers in code. These can be used in | |
| circumstances where very specific behavior is required. | |
| ### Language implementation | |
| For the most part, pointer authentication is an unobserved detail of the | |
| implementation of the programming language. Any element of the language | |
| implementation that would perform an indirect branch to a pointer is implicitly | |
| altered so that the pointer is signed when first constructed and authenticated | |
| when the branch is performed. This includes: | |
| - indirect-call features in the programming language, such as C function | |
| pointers, C++ virtual functions, C++ member function pointers, the "blocks" | |
| C extension, and so on; | |
| - returning from a function, no matter how it is called; and | |
| - indirect calls introduced by the implementation, such as branches through the | |
| global offset table (GOT) used to implement direct calls to functions defined | |
| outside of the current shared object. | |
| For more information about this, see the [Language ABI](#language-abi) section. | |
| However, some aspects of the implementation are observable by the programmer or | |
| otherwise require special notice. | |
| #### C data pointers | |
| The current implementation in Clang does not sign pointers to ordinary data by | |
| default. For a partial explanation of the reasoning behind this, see the | |
| [Theory of operation](#theory-of-operation) section. | |
| A specific data pointer which is more security-sensitive than most can be | |
| signed using the [`__ptrauth` qualifier](#ptrauth-qualifier) or using the `<ptrauth.h>` | |
| intrinsics. | |
| #### C function pointers | |
| The C standard imposes restrictions on the representation and semantics of | |
| function pointer types which make it difficult to achieve satisfactory | |
| signature diversity in the default language rules. See [Attacks on pointer | |
| authentication](#attacks-on-pointer-authentication) for more information about signature diversity. Programmers | |
| should strongly consider using the `__ptrauth` qualifier to improve the | |
| protections for important function pointers, such as the components of of | |
| a hand-rolled "v-table"; see the section on the [`__ptrauth` qualifier](#ptrauth-qualifier) for | |
| details. | |
| The value of a pointer to a C function includes a signature, even when the | |
| value is cast to a non-function-pointer type like `void*` or `intptr_t`. On | |
| implementations that use high bits to store the signature, this means that | |
| relational comparisons and hashes will vary according to the exact signature | |
| value, which is likely to change between executions of a program. In some | |
| implementations, it may also vary based on the exact function pointer type. | |
| #### Null pointers | |
| In principle, an implementation could derive the signed null pointer value | |
| simply by applying the standard signing algorithm to the raw null pointer | |
| value. However, for likely signing algorithms, this would mean that the signed | |
| null pointer value would no longer be statically known, which would have many | |
| negative consequences. For one, it would become substantially more expensive | |
| to emit null pointer values or to perform null-pointer checks. For another, | |
| the pervasive (even if technically unportable) assumption that null pointers | |
| are bitwise zero would be invalidated, making it substantially more difficult | |
| to adopt pointer authentication, as well as weakening common optimizations for | |
| zero-initialized memory such as the use of `.bzz` sections. Therefore it is | |
| beneficial to treat null pointers specially by giving them their usual | |
| representation. On AArch64, this requires additional code when working with | |
| possibly-null pointers, such as when copying a pointer field that has been | |
| signed with address diversity. | |
| While this representation of nulls is the safest option for the general case, | |
| there are some situations in which a null pointer may have important semantic | |
| or security impact. For that purpose Clang has the concept of a pointer | |
| authentication schema that signs and authenticates null values. | |
| #### Return addresses | |
| The current implementation in Clang implicitly signs the return addresses in | |
| function calls. While the value of the return address is technically an | |
| implementation detail of a function, there are some important libraries and | |
| development tools which rely on manually walking the chain of stack frames. | |
| These tools must be updated to correctly account for pointer authentication, | |
| either by stripping signatures (if security is not important for the tool, e.g. | |
| if it is capturing a stack trace during a crash) or properly authenticating | |
| them. More information about how these values are signed is available in the | |
| [Language ABI](#language-abi) section. | |
| #### C++ virtual functions | |
| The current implementation in Clang signs virtual function pointers with | |
| a discriminator derived from the full signature of the overridden method, | |
| including the method name and parameter types. It is possible to write C++ | |
| code that relies on v-table layout remaining constant despite changes to | |
| a method signature; for example, a parameter might be a `typedef` that | |
| resolves to a different type based on a build setting. Such code violates | |
| C++'s One Definition Rule (ODR), but that violation is not normally detected; | |
| however, pointer authentication will detect it. | |
| ### Language extensions | |
| #### Feature testing | |
| Whether the current target uses pointer authentication can be tested for with | |
| a number of different tests. | |
| - `__PTRAUTH__` macro is defined if `<ptrauth.h>` provides its normal | |
| interface. This implies support for the pointer authentication intrinsics | |
| and the `__ptrauth` qualifier. | |
| - `__has_feature(ptrauth_returns)` is true if the target uses pointer | |
| authentication to protect return addresses. | |
| - `__has_feature(ptrauth_calls)` is true if the target uses pointer | |
| authentication to protect indirect branches. On arm64e this implies | |
| `__has_feature(ptrauth_returns)`, `__has_feature(ptrauth_intrinsics)`, | |
| and the `__PTRAUTH__` macro. | |
| - For backwards compatibility purposes `__has_feature(ptrauth_intrinsics)` | |
| and `__has_feature(ptrauth_qualifier)` are available on arm64e targets. | |
| These features are synonymous with each other, and are equivalent to testing | |
| for the `__PTRAUTH__` macro definition. Use of these features should be | |
| restricted to cases where backwards compatibility is required, and should be | |
| paired with `defined(__PTRAUTH__)`. | |
| Clang provides several other tests only for historical purposes; for current | |
| purposes they are all equivalent to `ptrauth_calls`. | |
| #### `__ptrauth` qualifier | |
| `__ptrauth(key, address, discriminator)` is an extended type | |
| qualifier which causes so-qualified objects to hold pointers or pointer sized | |
| integers 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, or a pointer sized integer. 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 current implementation in Clang is known to not provide adequate safety | |
| guarantees against the creation of {ref}`signing oracles <signing-oracles>` when assigning data | |
| pointers to `__ptrauth`-qualified gl-values. See the section on [safe | |
| derivation](#safe-derivation) for more information. | |
| 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](#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-blend-discriminator). | |
| ##### Non-triviality from address diversity | |
| Address diversity must impose additional restrictions in order to allow the | |
| implementation to correctly copy values. In C++, a type qualified with address | |
| diversity is treated like a class type with non-trivial copy/move constructors | |
| and assignment operators, with the usual effect on containing classes and | |
| unions. C does not have a standard concept of non-triviality, and so we must | |
| describe the basic rules here, with the intention of imitating the emergent | |
| rules of C++: | |
| - A type may be **non-trivial to copy**. | |
| - A type may also be **illegal to copy**. Types that are illegal to copy are | |
| always non-trivial to copy. | |
| - A type may also be **address-sensitive**. This includes types that use self | |
| referencing pointers, data protected by address diversified pointer | |
| authentication, or other similar concepts. | |
| - A type qualified with a `ptrauth` qualifier or implicit authentication | |
| schema that requires address diversity is non-trivial to copy and | |
| address-sensitive. | |
| - An array type is illegal to copy, non-trivial to copy, or address-sensitive | |
| if its element type is illegal to copy, non-trivial to copy, or | |
| address-sensitive, respectively. | |
| - A struct type is illegal to copy, non-trivial to copy, or address-sensitive | |
| if it has a field whose type is illegal to copy, non-trivial to copy, or | |
| address-sensitive, respectively. | |
| - A union type is both illegal and non-trivial to copy if it has a field whose | |
| type is non-trivial or illegal to copy. | |
| - A union type is address-sensitive if it has a field whose type is | |
| address-sensitive. | |
| - A program is ill-formed if it uses a type that is illegal to copy as | |
| a function parameter, argument, or return type. | |
| - A program is ill-formed if an expression requires a type to be copied that is | |
| illegal to copy. | |
| - Otherwise, copying a type that is non-trivial to copy correctly copies its | |
| subobjects. | |
| - Types that are address-sensitive must always be passed and returned | |
| indirectly. Thus, changing the address-sensitivity of a type may be | |
| ABI-breaking even if its size and alignment do not change. | |
| ### `<ptrauth.h>` | |
| This header defines the following types and operations: | |
| #### `ptrauth_key` | |
| This `enum` is the type of abstract signing keys. In addition to defining | |
| the set of implementation-specific signing keys (for example, Armv8.3 defines | |
| `ptrauth_key_asia`), it also defines some portable aliases for those keys. | |
| For example, `ptrauth_key_function_pointer` is the key generally used for | |
| C function pointers, which will generally be suitable for other | |
| function-signing schemas. | |
| In all the operation descriptions below, key values must be constant values | |
| corresponding to one of the implementation-specific abstract signing keys from | |
| this `enum`. | |
| #### `ptrauth_extra_data_t` | |
| This is a `typedef` of a standard integer type of the correct size to hold | |
| a discriminator value. | |
| In the signing and authentication operation descriptions below, discriminator | |
| values must have either pointer type or integer type. If the discriminator is | |
| an integer, it will be coerced to `ptrauth_extra_data_t`. | |
| #### `ptrauth_blend_discriminator` | |
| ```c | |
| ptrauth_blend_discriminator(pointer, integer) | |
| ``` | |
| Produce a discriminator value which blends information from the given pointer | |
| and the given integer. | |
| Implementations may ignore some bits from each value, which is to say, the | |
| blending algorithm may be chosen for speed and convenience over theoretical | |
| strength as a hash-combining algorithm. For example, arm64e simply overwrites | |
| the high 16 bits of the pointer with the low 16 bits of the integer, which can | |
| be done in a single instruction with an immediate integer. | |
| `pointer` must have pointer type, and `integer` must have integer type. The | |
| result has type `ptrauth_extra_data_t`. | |
| #### `ptrauth_string_discriminator` | |
| ```c | |
| ptrauth_string_discriminator(string) | |
| ``` | |
| Compute a constant discriminator from the given string. | |
| `string` must be a string literal of `char` character type. The result has | |
| type `ptrauth_extra_data_t`. | |
| The result value is never zero and always within range for both the | |
| `__ptrauth` qualifier and `ptrauth_blend_discriminator`. | |
| This can be used in constant expressions. | |
| #### `ptrauth_type_discriminator` | |
| ```c | |
| ptrauth_type_discriminator(type) | |
| ``` | |
| Compute the constant discriminator derived from the given type, as is computed | |
| for automatically type diversified schemas. | |
| `type` must be a type. The result has the type `ptrauth_extra_data_t`. | |
| This can be used in constant expressions. | |
| #### `ptrauth_function_pointer_type_discriminator` | |
| ```c | |
| ptrauth_function_pointer_type_discriminator(function_type) | |
| ``` | |
| Compute the constant discriminator derived from the provided function type, for | |
| use in contexts where the default function authentication schema. If function | |
| pointer type diversity is enabled, this is equivalent to | |
| `ptrauth_type_discriminator(function_type)`, if it is not enabled this is `0`. | |
| `function_type` must be a function type. The result has the type `ptrauth_extra_data_t`. | |
| This can be used in constant expressions. | |
| #### `ptrauth_strip` | |
| ```c | |
| ptrauth_strip(signedPointer, key) | |
| ``` | |
| Given that `signedPointer` matches the layout for signed pointers signed with | |
| the given key, extract the raw pointer from it. This operation does not trap | |
| and cannot fail, even if the pointer is not validly signed. | |
| #### `ptrauth_sign_constant` | |
| ```c | |
| ptrauth_sign_constant(pointer, key, discriminator) | |
| ``` | |
| Return a signed pointer for a constant address in a manner which guarantees | |
| a non-attackable sequence. | |
| `pointer` must be a constant expression of pointer type which evaluates to | |
| a non-null pointer. | |
| `key` must be a constant expression of type `ptrauth_key`. | |
| `discriminator` must be a constant expression of pointer or integer type; | |
| if an integer, it will be coerced to `ptrauth_extra_data_t`. | |
| The result will have the same type as `pointer`. | |
| This can be used in constant expressions. | |
| #### `ptrauth_sign_unauthenticated` | |
| ```c | |
| ptrauth_sign_unauthenticated(pointer, key, discriminator) | |
| ``` | |
| Produce a signed pointer for the given raw pointer without applying any | |
| authentication or extra treatment. This operation is not required to have the | |
| same behavior on a null pointer that the language implementation would. | |
| This is a treacherous operation that can easily result in | |
| {ref}`signing oracles <signing-oracles>`. | |
| Programs should use it seldom and carefully. | |
| (ptrauth-auth-and-resign)= | |
| #### `ptrauth_auth_and_resign` | |
| ```c | |
| ptrauth_auth_and_resign(pointer, oldKey, oldDiscriminator, newKey, newDiscriminator) | |
| ``` | |
| Authenticate that `pointer` is signed with `oldKey` and | |
| `oldDiscriminator` and then resign the raw-pointer result of that | |
| authentication with `newKey` and `newDiscriminator`. | |
| `pointer` must have pointer type. The result will have the same type as | |
| `pointer`. This operation is not required to have the same behavior on | |
| a null pointer that the language implementation would. | |
| The code sequence produced for this operation must not be directly attackable. | |
| However, if the discriminator values are not constant integers, their | |
| computations may still be attackable. In the future, Clang should be enhanced | |
| to guarantee non-attackability if these expressions are | |
| {ref}`safely-derived <safe-derivation>`. | |
| #### `ptrauth_auth_with_pc_and_resign` | |
| ```c | |
| ptrauth_auth_with_pc_and_resign(pointer, oldKey, oldDiscriminator, oldPC, newKey, newDiscriminator) | |
| ``` | |
| Similar to {ref}`ptrauth_auth_and_resign <ptrauth-auth-and-resign>`, but additionally requires that the | |
| original signing schema includes the address of the signing instruction (i.e. | |
| uses `paciasppc` / `pacibsppc` instead of `paciasp` / `pacibsp`). This | |
| authenticates `pointer` signed with `oldKey` and `oldDiscriminator` at | |
| `oldPC`, then resigns the raw-pointer result with `newKey` and | |
| `newDiscriminator`. | |
| Note: `oldKey` must be `ptrauth_key_asia` (IA) or `ptrauth_key_asib` (IB), | |
| as only these keys support PC-based authentication instructions. Data keys | |
| (`ptrauth_key_asda` / `ptrauth_key_asdb`) are not supported. | |
| #### `ptrauth_auth_function` | |
| ```c | |
| ptrauth_auth_function(pointer, key, discriminator) | |
| ``` | |
| Authenticate that `pointer` is signed with `key` and `discriminator` and | |
| re-sign it to the standard schema for a function pointer of its type. | |
| `pointer` must have function pointer type. The result will have the same | |
| type as `pointer`. This operation is not required to have the same behavior | |
| on a null pointer that the language implementation would. | |
| This operation makes the same attackability guarantees as | |
| `ptrauth_auth_and_resign`. | |
| If this operation appears syntactically as the function operand of a call, | |
| Clang guarantees that the call will directly authenticate the function value | |
| using the given schema rather than re-signing to the standard schema. | |
| #### `ptrauth_auth_data` | |
| ```c | |
| ptrauth_auth_data(pointer, key, discriminator) | |
| ``` | |
| Authenticate that `pointer` is signed with `key` and `discriminator` and | |
| remove the signature. | |
| `pointer` must have object pointer type. The result will have the same type | |
| as `pointer`. This operation is not required to have the same behavior on | |
| a null pointer that the language implementation would. | |
| In the future when Clang makes safe derivation guarantees, the result of | |
| this operation should be considered safely-derived. | |
| #### `ptrauth_sign_generic_data` | |
| ```c | |
| ptrauth_sign_generic_data(value1, value2) | |
| ``` | |
| Computes a signature for the given pair of values, incorporating a secret | |
| signing key. | |
| This operation can be used to verify that arbitrary data has not been tampered | |
| with by computing a signature for the data, storing that signature, and then | |
| repeating this process and verifying that it yields the same result. This can | |
| be reasonably done in any number of ways; for example, a library could compute | |
| an ordinary checksum of the data and just sign the result in order to get the | |
| tamper-resistance advantages of the secret signing key (since otherwise an | |
| attacker could reliably overwrite both the data and the checksum). | |
| `value1` and `value2` must be either pointers or integers. If the integers | |
| are larger than `uintptr_t` then data not representable in `uintptr_t` may | |
| be discarded. | |
| The result will have type `ptrauth_generic_signature_t`, which is an integer | |
| type. Implementations are not required to make all bits of the result equally | |
| significant; in particular, some implementations are known to not leave | |
| meaningful data in the low bits. | |
| #### Standard `__ptrauth` qualifiers | |
| `<ptrauth.h>` additionally provides several macros which expand to | |
| `__ptrauth` qualifiers for common ABI situations. | |
| For convenience, these macros expand to nothing when pointer authentication is | |
| disabled. | |
| These macros can be found in the header; some details of these macros may be | |
| unstable or implementation-specific. | |
| ## Theory of operation | |
| The threat model of pointer authentication is as follows: | |
| - The attacker has the ability to read and write to a certain range of | |
| addresses, possibly the entire address space. However, they are constrained | |
| by the normal rules of the process: for example, they cannot write to memory | |
| that is mapped read-only, and if they access unmapped memory it will trigger | |
| a trap. | |
| - The attacker has no ability to add arbitrary executable code to the program. | |
| For example, the program does not include malicious code to begin with, and | |
| the attacker cannot alter existing instructions, load a malicious shared | |
| library, or remap writable pages as executable. If the attacker wants to get | |
| the process to perform a specific sequence of actions, they must somehow | |
| subvert the normal control flow of the process. | |
| In both of the above paragraphs, it is merely assumed that the attacker's | |
| *current* capabilities are restricted; that is, their current exploit does not | |
| directly give them the power to do these things. The attacker's immediate goal | |
| may well be to leverage their exploit to gain these capabilities, e.g. to load | |
| a malicious dynamic library into the process, even though the process does not | |
| directly contain code to do so. | |
| Note that any bug that fits the above threat model can be immediately exploited | |
| as a denial-of-service attack by simply performing an illegal access and | |
| crashing the program. Pointer authentication cannot protect against this. | |
| While denial-of-service attacks are unfortunate, they are also unquestionably | |
| the best possible result of a bug this severe. Therefore, pointer authentication | |
| enthusiastically embraces the idea of halting the program on a pointer | |
| authentication failure rather than continuing in a possibly-compromised state. | |
| Pointer authentication is a form of control-flow integrity (CFI) enforcement. | |
| The basic security hypothesis behind CFI enforcement is that many bugs can only | |
| be usefully exploited (other than as a denial-of-service) by leveraging them to | |
| subvert the control flow of the program. If this is true, then by inhibiting or | |
| limiting that subversion, it may be possible to largely mitigate the security | |
| consequences of those bugs by rendering them impractical (or, ideally, | |
| impossible) to exploit. | |
| Every indirect branch in a program has a purpose. Using human intelligence, a | |
| programmer can describe where a particular branch *should* go according to this | |
| purpose: a `return` in `printf` should return to the call site, a particular | |
| call in `qsort` should call the comparator that was passed in as an argument, | |
| and so on. But for CFI to enforce that every branch in a program goes where it | |
| *should* in this sense would require CFI to perfectly enforce every semantic | |
| rule of the program's abstract machine; that is, it would require making the | |
| programming environment perfectly sound. That is out of scope. Instead, the | |
| goal of CFI is merely to catch attempts to make a branch go somewhere that its | |
| obviously *shouldn't* for its purpose: for example, to stop a call from | |
| branching into the middle of a function rather than its beginning. As the | |
| information available to CFI gets better about the purpose of the branch, CFI | |
| can enforce tighter and tighter restrictions on where the branch is permitted to | |
| go. Still, ultimately CFI cannot make the program sound. This may help explain | |
| why pointer authentication makes some of the choices it does: for example, to | |
| sign and authenticate mostly code pointers rather than every pointer in the | |
| program. Preventing attackers from redirecting branches is both particularly | |
| important and particularly approachable as a goal. Detecting corruption more | |
| broadly is infeasible with these techniques, and the attempt would have far | |
| higher cost. | |
| ### Attacks on pointer authentication | |
| Pointer authentication works as follows. Every indirect branch in a program has | |
| a purpose. For every purpose, the implementation chooses a | |
| {ref}`signing schema <signing-schemas>`. At some place where a pointer is known | |
| to be correct for its purpose, it is signed according to the purpose's schema. | |
| At every place where the pointer is needed for its purpose, it is authenticated | |
| according to the purpose's schema. If that authentication fails, the program is | |
| halted. | |
| There are a variety of ways to attack this. | |
| #### Attacks of interest to programmers | |
| These attacks arise from weaknesses in the default protections offered by | |
| pointer authentication. They can be addressed by using attributes or intrinsics | |
| to opt in to stronger protection. | |
| ##### Substitution attacks | |
| An attacker can simply overwrite a pointer intended for one purpose with a | |
| pointer intended for another purpose if both purposes use the same signing | |
| schema and that schema does not use address diversity. | |
| The most common source of this weakness is when code relies on using the default | |
| language rules for C function pointers. The current implementation uses the | |
| exact same signing schema for all C function pointers, even for functions of | |
| substantially different type. While efforts are ongoing to improve constant | |
| diversity for C function pointers of different type, there are necessary limits | |
| to this. The C standard requires function pointers to be copyable with | |
| `memcpy`, which means that function pointers can never use address diversity. | |
| Furthermore, even if a function pointer can only be replaced with another | |
| function of the exact same type, that can still be useful to an attacker, as in | |
| the following example of a hand-rolled "v-table": | |
| ```c | |
| struct ObjectOperations { | |
| void (*retain)(Object *); | |
| void (*release)(Object *); | |
| void (*deallocate)(Object *); | |
| void (*logStatus)(Object *); | |
| }; | |
| ``` | |
| The weakness in this design is that by lacking any context specific | |
| discriminator, this means an attacker can substitute any of these fields with | |
| any other function pointer signed with the default schema. Similarly the lack of | |
| address diversity allows an attacker to replace the functions in one type's | |
| "v-table" with those of another. This can be mitigated by overriding the default | |
| authentication schema with a more specific signing schema for each purpose. For | |
| instance, in this example, the `__ptrauth` qualifier can be used with a | |
| different constant discriminator for each field. Since there's no particular | |
| reason it's important for this v-table to be copyable with `memcpy`, the | |
| functions can also be signed with address diversity: | |
| ```c | |
| #if defined(__PTRAUTH__) | |
| #define objectOperation(discriminator) \ | |
| __ptrauth(ptrauth_key_function_pointer, 1, discriminator) | |
| #else | |
| #define objectOperation(discriminator) | |
| #endif | |
| struct ObjectOperations { | |
| void (*objectOperation(0xf017) retain)(Object *); | |
| void (*objectOperation(0x2639) release)(Object *); | |
| void (*objectOperation(0x8bb0) deallocate)(Object *); | |
| void (*objectOperation(0xc5d4) logStatus)(Object *); | |
| }; | |
| ``` | |
| This weakness can also sometimes be mitigated by simply keeping the signed | |
| pointer in constant memory, but this is less effective than using better signing | |
| diversity. | |
| (access-path-attacks)= | |
| ##### Access path attacks | |
| If a signed pointer is often accessed indirectly (that is, by first loading the | |
| address of the object where the signed pointer is stored), an attacker can | |
| affect uses of it by overwriting the intermediate pointer in the access path. | |
| The most common scenario exhibiting this weakness is an object with a pointer to | |
| a "v-table" (a structure holding many function pointers). An attacker does not | |
| need to replace a signed function pointer in the v-table if they can instead | |
| simply replace the v-table pointer in the object with their own pointer --- | |
| perhaps to memory where they've constructed their own v-table, or to existing | |
| memory that coincidentally happens to contain a signed pointer at the right | |
| offset that's been signed with the right signing schema. | |
| This attack arises because data pointers are not signed by default. It works | |
| even if the signed pointer uses address diversity: address diversity merely | |
| means that each pointer is signed with its own storage address, | |
| which (by design) is invariant to changes in the accessing pointer. | |
| Using sufficiently diverse signing schemas within the v-table can provide | |
| reasonably strong mitigation against this weakness. Always use address and type | |
| diversity in v-tables to prevent attackers from assembling their own v-table. | |
| Avoid re-using constant discriminators to prevent attackers from replacing a | |
| v-table pointer with a pointer to totally unrelated memory that just happens to | |
| contain an similarly-signed pointer, or reused memory containing a different | |
| type. | |
| Further mitigation can be attained by signing pointers to v-tables. Any | |
| signature at all should prevent attackers from forging v-table pointers; they | |
| will need to somehow harvest an existing signed pointer from elsewhere in | |
| memory. Using a meaningful constant discriminator will force this to be | |
| harvested from an object with similar structure (e.g. a different implementation | |
| of the same interface). Using address diversity will prevent such harvesting | |
| entirely. However, care must be taken when sourcing the v-table pointer | |
| originally; do not blindly sign a pointer that is not | |
| {ref}`safely derived <safe-derivation>`. | |
| (signing-oracles)= | |
| ##### Signing oracles | |
| A signing oracle is a bit of code which can be exploited by an attacker to sign | |
| an arbitrary pointer in a way that can later be recovered. Such oracles can be | |
| used by attackers to forge signatures matching the oracle's signing schema, | |
| which is likely to cause a total compromise of pointer authentication's | |
| effectiveness. | |
| This attack only affects ordinary programmers if they are using certain | |
| treacherous patterns of code. Currently this includes: | |
| - all uses of the `__ptrauth_sign_unauthenticated` intrinsic and | |
| - assigning values to `__ptrauth`-qualified l-values. | |
| Care must be taken in these situations to ensure that the pointer being signed | |
| has been {ref}`safely derived <safe-derivation>` or is otherwise not possible to | |
| attack. (In some cases, this may be challenging without compiler support.) | |
| A diagnostic will be added in the future for implicitly dangerous patterns of | |
| code, such as assigning a non-safely-derived values to a | |
| `__ptrauth`-qualified l-value. | |
| (authentication-oracles)= | |
| ##### Authentication oracles | |
| An authentication oracle is a bit of code which can be exploited by an attacker | |
| to leak whether a signed pointer is validly signed without halting the program | |
| if it isn't. Such oracles can be used to forge signatures matching the oracle's | |
| signing schema if the attacker can repeatedly invoke the oracle for different | |
| candidate signed pointers. This is likely to cause a total compromise of pointer | |
| authentication's effectiveness. | |
| There should be no way for an ordinary programmer to create an authentication | |
| oracle using the current set of operations. However, implementation flaws in the | |
| past have occasionally given rise to authentication oracles due to a failure to | |
| immediately trap on authentication failure. | |
| The likelihood of creating an authentication oracle is why there is currently no | |
| intrinsic which queries whether a signed pointer is validly signed. | |
| #### Attacks of interest to implementors | |
| These attacks are not inherent to the model; they arise from mistakes in either | |
| implementing or using the `sign` and `auth` operations. Avoiding these mistakes | |
| requires careful work throughout the system. | |
| ##### Failure to trap on authentication failure | |
| Any failure to halt the program on an authentication failure is likely to be | |
| exploitable by attackers to create an | |
| {ref}`authentication oracle <authentication-oracles>`. | |
| There are several different ways to introduce this problem: | |
| - The implementation might try to halt the program in some way that can be | |
| intercepted. | |
| For example, the Armv8.3 `aut` instructions do not directly trap on | |
| authentication failure on processors that lack the `FPAC` extension. | |
| Instead, they corrupt their results to be invalid pointers, with the idea that | |
| subsequent uses of those pointers will trigger traps as bad memory accesses. | |
| However, most kernels do not immediately halt programs that trap due to bad | |
| memory accesses; instead, they notify the process to give it an opportunity to | |
| recover. If this happens with an `auth` failure, the attacker may be able to | |
| exploit the recovery path in a way that creates an oracle. Kernels must | |
| provide a way for a process to trap unrecoverably, and this should cover all | |
| `FPAC` traps. Compilers must ensure that `auth` failures trigger an | |
| unrecoverable trap, ideally by taking advantage of `FPAC`, but if necessary | |
| by emitting extra instructions. | |
| - A compiler might use an intermediate representation (IR) for `sign` and | |
| `auth` operations that cannot make adequate correctness guarantees. | |
| For example, suppose that an IR uses ARMv8.3-like semantics for `auth`: the | |
| operation merely corrupts its result on failure instead of promising to trap. | |
| A frontend might emit patterns of IR that always follow an `auth` with a | |
| memory access, thinking that this ensures correctness. But if the IR can be | |
| transformed to insert code between the `auth` and the access, or if the | |
| `auth` can be speculated, then this potentially creates an oracle. It is | |
| better for `auth` to semantically guarantee to trap, potentially requiring | |
| an explicit check in the generated code. An ARMv8.3-like target can avoid this | |
| explicit check in the common case by recognizing the pattern of an `auth` | |
| followed immediately by an access. | |
| ##### Attackable code sequences | |
| If code that is part of a pointer authentication operation is interleaved with | |
| code that may itself be vulnerable to attacks, an attacker may be able to use | |
| this to create a {ref}`signing <signing-oracles>` or | |
| {ref}`authentication <authentication-oracles>` oracle. | |
| For example, suppose that the compiler is generating a call to a function and | |
| passing two arguments: a signed constant pointer and a value derived from a | |
| call. In ARMv8.3, this code might look like so: | |
| ```asm | |
| adr x19, _callback. ; compute &_callback | |
| paciza x19 ; sign it with a constant discriminator of 0 | |
| blr _argGenerator ; call _argGenerator() (returns in x0) | |
| mov x1, x0 ; move call result to second arg register | |
| mov x0, x19 ; move signed &_callback to first arg register | |
| blr _function ; call _function | |
| ``` | |
| This code is correct, as would be a sequencing that does *both* the `adr` and | |
| the `paciza` after the call to `_argGenerator`. But a sequence that | |
| computes the address of `_callback` but leaves it as a raw pointer in a | |
| register during the call to `_argGenerator` would be vulnerable: | |
| ```asm | |
| adr x19, _callback. ; compute &_callback | |
| blr _argGenerator ; call _argGenerator() (returns in x0) | |
| mov x1, x0 ; move call result to second arg register | |
| paciza x19 ; sign &_callback | |
| mov x0, x19 ; move signed &_callback to first arg register | |
| blr _function ; call _function | |
| ``` | |
| If `_argGenerator` spills `x19` (a callee-save register), and if the | |
| attacker can perform a write during this call, then the attacker can overwrite | |
| the spill slot with an arbitrary pointer that will eventually be unconditionally | |
| signed after the function returns. This would be a signing oracle. | |
| The implementation can avoid this by obeying two basic rules: | |
| - The compiler's intermediate representations (IR) should not provide operations | |
| that expose intermediate raw pointers. This may require providing extra | |
| operations that perform useful combinations of operations. | |
| For example, there should be an "atomic" auth-and-resign operation that should | |
| be used instead of emitting an `auth` operation whose result is fed into a | |
| `sign`. | |
| Similarly, if a pointer should be authenticated as part of doing a memory | |
| access or a call, then the access or call should be decorated with enough | |
| information to perform the authentication; there should not be a separate | |
| `auth` whose result is used as the pointer operand for the access or call. | |
| (In LLVM IR, we do this for calls, but not yet for loads or stores.) | |
| "Operations" includes things like materializing a signed value to a known | |
| function or global variable. The compiler must be able to recognize and emit | |
| this as a unified operation, rather than potentially splitting it up as in | |
| the example above. | |
| - The compiler backend should not be too aggressive about scheduling | |
| instructions that are part of a pointer authentication operation. This may | |
| require custom code-generation of these operations in some cases. | |
| ##### Register clobbering | |
| As a refinement of the section on [Attackable code sequences](#attackable-code-sequences), if the attacker | |
| has the ability to modify arbitrary *register* state at arbitrary points in the | |
| program, then special care must be taken. | |
| For example, ARMv8.3 might materialize a signed function pointer like so: | |
| ```asm | |
| adr x0, _callback. ; compute &_callback | |
| paciza x0 ; sign it with a constant discriminator of 0 | |
| ``` | |
| If an attacker has the ability to overwrite `x0` between these two | |
| instructions, this code sequence is vulnerable to becoming a signing oracle. | |
| For the most part, this sort of attack is not possible: it is a basic element of | |
| the design of modern computation that register state is private and inviolable. | |
| However, in systems that support asynchronous interrupts, this property requires | |
| the cooperation of the interrupt-handling code. If that code saves register | |
| state to memory, and that memory can be overwritten by an attacker, then | |
| essentially the attack can overwrite arbitrary register state at an arbitrary | |
| point. This could be a concern if the threat model includes attacks on the | |
| kernel or if the program uses user-space preemptive multitasking. | |
| (Readers might object that an attacker cannot rely on asynchronous interrupts | |
| triggering at an exact instruction boundary. In fact, researchers have had some | |
| success in doing exactly that. Even ignoring that, though, we should aim to | |
| protect against lucky attackers just as much as good ones.) | |
| To protect against this, saved register state must be at least partially signed | |
| (using something like [`ptrauth_sign_generic_data`](#ptrauth-sign-generic-data)). This is required for | |
| correctness anyway because saved thread states include security-critical | |
| registers such as SP, FP, PC, and LR (where applicable). Ideally, this | |
| signature would cover all the registers, but since saving and restoring | |
| registers can be very performance-sensitive, that may not be acceptable. It is | |
| sufficient to set aside a small number of scratch registers that will be | |
| guaranteed to be preserved correctly; the compiler can then be careful to only | |
| store critical values like intermediate raw pointers in those registers. | |
| `setjmp` and `longjmp` should sign and authenticate the core registers (SP, | |
| FP, PC, and LR), but they do not need to worry about intermediate values because | |
| `setjmp` can only be called synchronously, and the compiler should never | |
| schedule pointer-authentication operations interleaved with arbitrary calls. | |
| (relative-addresses)= | |
| ##### Attacks on relative addressing | |
| Relative addressing is a technique used to compress and reduce the load-time | |
| cost of infrequently-used global data. The pointer authentication system is | |
| unlikely to support signing or authenticating a relative address, and in most | |
| cases it would defeat the point to do so: it would take additional storage | |
| space, and applying the signature would take extra work at load time. | |
| Relative addressing is not precluded by the use of pointer authentication, but | |
| it does take extra considerations to make it secure: | |
| - Relative addresses must only be stored in read-only memory. A writable | |
| relative address can be overwritten to point nearly anywhere, making it | |
| inherently insecure; this danger can only be compensated for with techniques | |
| for protecting arbitrary data like [`ptrauth_sign_generic_data`](#ptrauth-sign-generic-data). | |
| - Relative addresses must only be accessed through signed pointers with adequate | |
| diversity. If an attacker can perform an `access path attack` to replace the | |
| pointer through which the relative address is accessed, they can easily cause | |
| the relative address to point wherever they want. | |
| ##### Signature forging | |
| If an attacker can exactly reproduce the behavior of the signing algorithm, and | |
| they know all the correct inputs to it, then they can perfectly forge a | |
| signature on an arbitrary pointer. | |
| There are three components to avoiding this mistake: | |
| - The abstract signing algorithm should be good: it should not have glaring | |
| flaws which would allow attackers to predict its result with better than | |
| random accuracy without knowing all the inputs (like the key values). | |
| - The key values should be kept secret. If at all possible, they should never | |
| be stored in accessible memory, or perhaps only stored encrypted. | |
| - Contexts that are meant to be independently protected should use different | |
| key values. For example, the kernel should not use the same keys as user | |
| processes. Different user processes should also use different keys from each | |
| other as much as possible, although this may pose its own technical | |
| challenges. | |
| ##### Remapping | |
| If an attacker can change the memory protections on certain pages of the | |
| program's memory, that can substantially weaken the protections afforded by | |
| pointer authentication. | |
| - If an attacker can inject their own executable code, they can also certainly | |
| inject code that can be used as a {ref}`signing oracle <signing-oracles>`. | |
| The same is true if they can write to the instruction stream. | |
| - If an attacker can remap read-only program data sections to be writable, then | |
| any use of {ref}`relative addresses <relative-addresses>` in global data becomes insecure. | |
| - On platforms that use them, if an attacker can remap the memory containing | |
| the [global offset tables](#global-offset-tables) as writable, then any unsigned pointers in those | |
| tables are insecure. | |
| Remapping memory in this way often requires the attacker to have already | |
| substantively subverted the control flow of the process. Nonetheless, if the | |
| operating system has a mechanism for mapping pages in a way that cannot be | |
| remapped, this should be used wherever possible. | |
| (safe-derivation)= | |
| ### Safe derivation | |
| Whether a data pointer is stored, even briefly, as a raw pointer can affect the | |
| security-correctness of a program. (Function pointers are never implicitly | |
| stored as raw pointers; raw pointers to functions can only be produced with the | |
| `<ptrauth.h>` intrinsics.) Repeated re-signing can also impact performance. | |
| Clang makes a modest set of guarantees in this area: | |
| - An expression of pointer type is said to be **safely derived** if: | |
| - it takes the address of a global variable or function, or | |
| - it is a load from a gl-value of `__ptrauth`-qualified type, or | |
| - it is a load from read-only memory that has been initialized from a safely | |
| derived source, such as the `data const` section of a binary or library. | |
| - If a value that is safely derived is assigned to a `__ptrauth`-qualified | |
| object, including by initialization, then the value will be directly signed as | |
| appropriate for the target qualifier and will not be stored as a raw pointer. | |
| - If the function expression of a call is a gl-value of `__ptrauth`-qualified | |
| type, then the call will be authenticated directly according to the source | |
| qualifier and will not be resigned to the default rule for a function pointer | |
| of its type. | |
| These guarantees are known to be inadequate for data pointer security. In | |
| particular, Clang should be enhanced to make the following guarantees: | |
| - A pointer should additionally be considered safely derived if it is: | |
| - the address of a gl-value that is safely derived, | |
| - the result of pointer arithmetic on a pointer that is safely derived (with | |
| some restrictions on the integer operand), | |
| - the result of a comma operator where the second operand is safely derived, | |
| - the result of a conditional operator where the selected operand is safely | |
| derived, or | |
| - the result of loading from a safely derived gl-value. | |
| - A gl-value should be considered safely derived if it is: | |
| - a dereference of a safely derived pointer, | |
| - a member access into a safely derived gl-value, or | |
| - a reference to a variable. | |
| - An access to a safely derived gl-value should be guaranteed to not allow | |
| replacement of any of the safely-derived component values at any point in the | |
| access. "Access" should include loading a function pointer. | |
| - Assignments should include pointer-arithmetic operators like `+=`. | |
| Making these guarantees will require further work, including significant new | |
| support in LLVM IR. | |
| Furthermore, Clang should implement a warning when assigning a data pointer that | |
| is not safely derived to a `__ptrauth`-qualified gl-value. | |
| ## Language ABI | |
| This section describes the pointer-authentication ABI currently implemented in | |
| Clang for the Apple arm64e target. As other targets adopt pointer | |
| authentication, this section should be generalized to express their ABIs as | |
| well. | |
| ### Key assignments | |
| ARMv8.3 provides four abstract signing keys: `IA`, `IB`, `DA`, and `DB`. | |
| The architecture designates `IA` and `IB` for signing code pointers and | |
| `DA` and `DB` for signing data pointers; this is reinforced by two | |
| properties: | |
| - The ISA provides instructions that perform combined auth+call and auth+load | |
| operations; these instructions can only use the `I` keys and `D` keys, | |
| respectively. | |
| - AArch64's TBI feature can be separately enabled for code pointers (controlling | |
| whether indirect-branch instructions ignore those bits) and data pointers | |
| (controlling whether memory-access instructions) ignore those bits. If TBI is | |
| enabled for a kind of pointer, the sign and auth operations preserve the TBI | |
| bits when signing with an associated keys (at the cost of shrinking the number | |
| of available signing bits by 8). | |
| arm64e then further subdivides the keys as follows: | |
| - The `A` keys are used for primarily "global" purposes like signing v-tables | |
| and function pointers. These keys are sometimes called *process-independent* | |
| or *cross-process* because on existing OSes they are not changed when changing | |
| processes, although this is not a platform guarantee. | |
| - The `B` keys are used for primarily "local" purposes like signing return | |
| addresses. These keys are sometimes called *process-specific* because they | |
| are typically different between processes. However, they are in fact shared | |
| across processes in one situation: systems which provide `fork` cannot | |
| change these keys in the child process; they can only be changed during | |
| `exec`. | |
| ### Implementation-defined algorithms and quantities | |
| The cryptographic hash algorithm used to compute signatures in ARMv8.3 is a | |
| private detail of the hardware implementation. | |
| arm64e restricts constant discriminators (used in `__ptrauth` and | |
| `ptrauth_blend_discriminator`) to the range from 0 to 65535, inclusive. A 0 | |
| discriminator generally signifies that no blending is required; see the | |
| documentation for `ptrauth_blend_discriminator`. This range is somewhat | |
| narrow but has two advantages: | |
| - The AArch64 ISA allows an arbitrary 16-bit immediate to be written over the | |
| top 16 bits of a register in a single instruction: | |
| ```asm | |
| movk xN, #0x4849, LSL 48 | |
| ``` | |
| This is ideal for the discriminator blending operation because it adds minimal | |
| code-size overhead and avoids overwriting any interesting bits from the | |
| pointer. Blending in a wider constant discriminator would either clobber | |
| interesting bits (e.g. if it was loaded with `movk xN, #0x4c4f, LSL 32`) or | |
| require significantly more code (e.g. if the discriminator was loaded with a | |
| `mov+bfi` sequence). | |
| - It is possible to pack a 16-bit discriminator into loader metadata with | |
| minimal compromises, whereas a wider discriminator would require extra | |
| metadata storage and therefore significantly impact load times. | |
| The string hash used by `ptrauth_string_discriminator` is a 64-bit SipHash-2-4 | |
| using the constant seed `b5d4c9eb79104a796fec8b1b428781d4` (big-endian), with | |
| the result reduced by modulo to the range of non-zero discriminators (i.e. | |
| `(rawHash % 65535) + 1`). | |
| ### Return addresses | |
| The kernel must ensure that attackers cannot replace LR due to an asynchronous | |
| exception; see [Register clobbering](#register-clobbering). If this is done by generally protecting | |
| LR, then functions which don't spill LR to the stack can avoid signing it | |
| entirely. Otherwise, the return address must be signed; on arm64e it is signed | |
| with the `IB` key using the stack pointer on entry as the discriminator. | |
| Protecting return addresses is of such particular importance that the `IB` key | |
| is almost entirely reserved for this purpose. | |
| ### Global offset tables | |
| The global offset table (GOT) is not part of the language ABI, but it is a | |
| common implementation technique for dynamic linking which deserves special | |
| discussion here. | |
| Whenever possible, signed pointers should be materialized directly in code | |
| rather than via the GOT, e.g. using an `adrp+add+pac` sequence on ARMv8.3. | |
| This decreases the amount of work necessary at load time to initialize the GOT, | |
| but more importantly, it defines away the potential for several attacks: | |
| - Attackers cannot change instructions, so there is no way to cause this code | |
| sequence to materialize a different pointer, whereas an access via the GOT | |
| always has *at minimum* a probabilistic chance to be the target of successful | |
| [substitution attacks](#substitution-attacks). | |
| - The GOT is a dense pool of fixed pointers at a fixed offset relative to code; | |
| attackers can search this pool for useful pointers that can be used in | |
| [substitution attacks](#substitution-attacks), whereas pointers that are only materialized directly | |
| are not so easily available. | |
| - Similarly, attackers can use {ref}`access path attacks <access-path-attacks>` to replace a pointer to a | |
| signed pointer with a pointer to the GOT if the signing schema used within the | |
| GOT happens to be the same as the original pointer. This kind of collision | |
| becomes much less likely to be useful the fewer pointers are in the GOT in the | |
| first place. | |
| If this can be done for a symbol, then the compiler need only ensure that it | |
| materializes the signed pointer using registers that are safe against | |
| [register clobbering](#register-clobbering). | |
| However, many symbols can only be accessed via the GOT, e.g. because they | |
| resolve to definitions outside of the current image. In this case, care must | |
| be taken to ensure that using the GOT does not introduce weaknesses. | |
| - If the entire GOT can be mapped read-only after loading, then no signing is | |
| required within the GOT. In fact, not signing pointers in the GOT is | |
| preferable in this case because it makes the GOT useless for the harvesting | |
| and access-path attacks above. Storing raw pointers in this way is usually | |
| extremely unsafe, but for the special case of an immutable GOT entry it's fine | |
| because the GOT is always accessed via an address that is directly | |
| materialized in code and thus provably unattackable. (But see [Remapping](#remapping).) | |
| - Otherwise, GOT entries which are used for producing a signed pointer constant | |
| must be signed. The signing schema used in the GOT need not match the target | |
| signing schema for the signed constant. To counteract the threats of | |
| substitution attacks, it's best if GOT entries can be signed with address | |
| diversity. Using a good constant discriminator as well (perhaps derived from | |
| the symbol name) can make it less useful to use a pointer to the GOT as the | |
| replacement in an {ref}`access path attack <access-path-attacks>`. | |
| In either case, the compiler must ensure that materializing the address of a GOT | |
| entry as part of producing a signed pointer constant is not vulnerable to | |
| [register clobbering](#register-clobbering). If the linker also generates code for this, e.g. for | |
| call stubs, this generated code must take the same precautions. | |
| ### Dynamic symbol lookup | |
| On platforms that support dynamically loading or resolving symbols it is | |
| necessary for them to define the pointer authentication semantics of the APIs | |
| provided to perform such lookups. While the platform may choose to reply | |
| unsigned pointers from such function and rely on the caller performing the | |
| initial signing, doing so creates the opportunity for caller side errors that | |
| create {ref}`signing oracles <signing-oracles>`. | |
| On arm64e the `dlsym` function is used to resolve a symbol at runtime. If the | |
| resolved symbol is a function or other code pointer the returned pointer is | |
| signed using the default function signing schema described in | |
| {ref}`C function pointers <c-function-abi>`. If the resolved symbol is not a code pointer it is | |
| returned as an unsigned pointer. | |
| (c-function-abi)= | |
| ### C function pointers | |
| On arm64e, C function pointers are currently signed with the `IA` key without | |
| address diversity and with a constant discriminator of 0. | |
| The C and C++ standards do not permit C function pointers to be signed with | |
| address diversity by default: in C++ terms, function pointer types are required | |
| to be trivially copyable, which means they must be copyable with `memcpy`. | |
| The use of a uniform constant discriminator greatly simplifies the adoption of | |
| arm64e, but it is a significant weakness in the mitigation because it allows any | |
| C function pointer to be replaced with another. Clang supports | |
| `-fptrauth-function-pointer-type-discrimination`, which enables a variant ABI | |
| that uses type discrimination for function pointers. When generating the type | |
| based discriminator for a function type all primitive integer types are | |
| considered equivalent due to the prevalence of mismatching integer parameter | |
| types in real world code. Type discrimination of function pointers is | |
| ABI-incompatible with the standard arm64e ABI, but it can be used in constrained | |
| contexts such as embedded systems or in code that does not require function | |
| pointer interoperation with the standard ABI (e.g. because it does not pass | |
| function pointers back and forth, or only does so through | |
| `__ptrauth`-qualified l-values). | |
| ### C++ virtual tables | |
| By default the pointer to a C++ virtual table is currently signed with the | |
| `DA` key, address diversity, and a constant discriminator equal to the string | |
| hash (see [`ptrauth_string_discriminator`](#ptrauth-string-discriminator)) of the mangled v-table identifier | |
| of the primary base class for the v-table. To support existing code or ABI | |
| constraints it is possible to use the `ptrauth_vtable_pointer` attribute to | |
| override the schema used for the v-table pointer of the base type of | |
| polymorphic class hierarchy. This attribute permits the configuration of the | |
| key, address diversity mode, and any extra constant discriminator to be used. | |
| Like object v-table pointers, pointers to virtual tables stored in VTTs are | |
| signed with the `DA` key. By default, no additional address or type | |
| discrimination is applied. When `-fptrauth-vtt-vtable-pointer-discrimination` | |
| is enabled, they are additionally discriminated by address and/or type, | |
| as controlled by the `-fptrauth-vtable-pointer-{address,type}-discrimination` | |
| options. Address discrimination uses the address of the corresponding VTT entry. | |
| Type discrimination uses the string hash (see [`ptrauth_string_discriminator`](#ptrauth-string-discriminator)) | |
| of the mangled v-table identifier of the current class with an appended `_VTT` | |
| suffix. Unlike object v-table pointers, explicit schema customization for VTT | |
| entries is currently not supported. | |
| Virtual functions in a C++ virtual table are signed with the `IA` key, address | |
| diversity, and a constant discriminator equal to the string hash (see | |
| [`ptrauth_string_discriminator`](#ptrauth-string-discriminator)) of the mangled name of the function which | |
| originally gave rise to the v-table slot. | |
| ### C++ dynamic_cast | |
| C++'s `dynamic_cast` presents a difficulty relative to other polymorphic | |
| languages that have a | |
| `top type <https://en.wikipedia.org/wiki/Any_type>` as the use of declaration | |
| diversity for v-table pointers results in distinct signing schemas for each | |
| isolated type hierarchy. As a result it is not possible for the Itanium ABI | |
| defined `__dynamic_cast` entry point to directly authenticate the v-table | |
| pointer of the provided object. | |
| The current implementation uses a forced authentication of the subject object's | |
| v-table prior to invoking `__dynamic_cast` to partially verify that the | |
| object's vtable is valid. The `__dynamic_cast` implementation currently relies | |
| on this caller side check to limit the substitutability of the v-table pointer | |
| with an incorrect or invalid v-table. The subsequent implementation of the | |
| dynamic cast algorithm is built on pointer auth protected `type_info` objects. | |
| In future a richer solution may be developed to support vending the correct | |
| authentication schema directly to the `dynamic_cast` implementation. | |
| ### C++ std::type_info v-table pointers | |
| The v-table pointer of the `std::type_info` type is signed with the `DA` key | |
| and no additional diversity. | |
| ### C++ member function pointers | |
| A member function pointer is signed with the `IA` key, no address diversity, | |
| and a constant discriminator equal to the string hash | |
| (see [`ptrauth_string_discriminator`](#ptrauth-string-discriminator)) of the member pointer type. Address | |
| diversity is not permitted by C++ for member function pointers because they must | |
| be trivially-copyable types. | |
| The Itanium C++ ABI specifies that member function pointers to virtual functions | |
| simply store an offset to the correct v-table slot. This ABI cannot be used | |
| securely with pointer authentication because there is no safe place to store the | |
| constant discriminator for the target v-table slot: if it's stored with the | |
| offset, an attacker can simply overwrite it with the right discriminator for the | |
| offset. Even if the programmer never uses pointers to virtual functions, the | |
| existence of this code path makes all member function pointer dereferences | |
| insecure. | |
| arm64e changes this ABI so that virtual function pointers are stored using | |
| dispatch thunks with vague linkage. Because arm64e supports interoperation with | |
| `arm64` code when pointer authentication is disabled, an arm64e member | |
| function pointer dereference still recognizes the virtual-function | |
| representation but uses an bogus discriminator on that path that should always | |
| trap if pointer authentication is enabled dynamically. | |
| The use of dispatch thunks means that `==` on member function pointers is no | |
| longer reliable for virtual functions, but this is acceptable because the | |
| standard makes no guarantees about it in the first place. | |
| The use of dispatch thunks also is required to support declaration specific | |
| authentication schemas for v-table pointers. | |
| ### C++ mangling | |
| When the `__ptrauth` qualifier appears in a C++ mangled name, | |
| it is mangled as a vendor qualifier with the signature | |
| `U9__ptrauthILj<key>ELb<addressDiscriminated>ELj<extraDiscriminator>EE`. | |
| e.g. `int * __ptrauth(1, 0, 1234)` will be mangled as | |
| `U9__ptrauthILj1ELb0ELj1234EE`. | |
| If the vtable pointer authentication scheme of a polymorphic class is overridden | |
| we mangle the override information with the vendor qualifier | |
| `__vtptrauth(int key, bool addressDiscriminated, unsigned extraDiscriminator)`, | |
| where the extra discriminator is the explicit value the specified discrimination | |
| mode evalutes to. | |
| ### Blocks | |
| Block pointers are data pointers which must interoperate with the ObjC `id` type | |
| and therefore cannot be signed themselves. As blocks conform to the ObjC `id` | |
| type, they contain an `isa` pointer signed as described | |
| {ref}`below <objc-isa-and-super>`. | |
| The invocation pointer in a block is signed with the `IA` key using address | |
| diversity and a constant discriminator of 0. Using a uniform discriminator is | |
| seen as a weakness to be potentially improved, but this is tricky due to the | |
| subtype polymorphism directly permitted for blocks. | |
| Block descriptors and `__block` variables can contain pointers to functions | |
| that can be used to copy or destroy the object. These functions are signed with | |
| the `IA` key, address diversity, and a constant discriminator of 0. The | |
| structure of block descriptors is under consideration for improvement. | |
| ### Objective-C runtime | |
| In addition to the compile time ABI design, the Objective-C runtime provides | |
| additional protection to methods and other metadata that have been loaded into | |
| the Objective-C method cache; this protection is private to the runtime. | |
| ### Objective-C methods | |
| Objective-C method lists sign methods with the `IA` key using address | |
| diversity and a constant discriminator of 0. Using a uniform constant | |
| discriminator is believed to be acceptable because these tables are only | |
| accessed internally to the Objective-C runtime. | |
| ### Objective-C class method list pointer | |
| The method list pointer in Objective-C classes are signed with the `DA` key | |
| using address diversity, and a constant discriminator of 0xC310. | |
| ### Objective-C class read-only data pointer | |
| The read-only data pointer in Objective-C classes are signed with the `DA` key | |
| using address diversity, and a constant discriminator of 0x61F8. | |
| (objc-isa-and-super)= | |
| ### Objective-C `isa` and `super` pointers | |
| An Objective-C object's `isa` and `super` pointers are both signed with | |
| the `DA` key using address diversity and constant discriminators of 0x6AE1 | |
| and 0x25DA respectively. | |
| ### Objective-C `SEL` pointers | |
| By default, the type of an Objective-C instance variable of type `SEL`, when | |
| the qualifiers do not include an explicit `__ptrauth` qualifier, is adjusted | |
| to be qualified with `__ptrauth(ptrauth_key_asdb, 1, 0x57C2)`. | |
| This provides a measure of implicit at-rest protection to Objective-C classes | |
| that store selectors, as in the common target-action design pattern. This | |
| prevents attackers from overriding the selector to invoke an arbitrary different | |
| method, which is a major attack vector in Objective-C. Since `SEL` values are | |
| not normally passed around as signed pointers, there is a | |
| {ref}`signing oracle <signing-oracles>` associated with the initialization of the | |
| ivar, but the use of address and constant diversity limit the risks. | |
| The implicit qualifier means that the type of the ivar does not match its | |
| declaration, which can cause type errors if the address of the ivar is taken: | |
| ```ObjC | |
| @interface A : NSObject { | |
| SEL _s; | |
| } | |
| @end | |
| void f(SEL *); | |
| @implementation A | |
| -(void)g | |
| { | |
| f(&_s); | |
| } | |
| @end | |
| ``` | |
| To fix such a mismatch the schema macro from `<ptrauth.h>`: | |
| ```ObjC | |
| #include <ptrauth.h> | |
| void f(SEL __ptrauth_objc_sel*); | |
| ``` | |
| or less safely, and introducing the possibility of an | |
| {ref}`signing or authentication oracle <signing-oracles>`, an unauthenticated | |
| temporary may be used as intermediate storage. | |
| ## Alternative implementations | |
| ### Signature storage | |
| It is not critical for the security of pointer authentication that the | |
| signature be stored "together" with the pointer, as it is in Armv8.3. An | |
| implementation could just as well store the signature in a separate word, so | |
| that the `sizeof` a signed pointer would be larger than the `sizeof` a raw | |
| pointer. | |
| Storing the signature in the high bits, as Armv8.3 does, has several trade-offs: | |
| - Disadvantage: there are substantially fewer bits available for the signature, | |
| weakening the mitigation by making it much easier for an attacker to simply | |
| guess the correct signature. | |
| - Disadvantage: future growth of the address space will necessarily further | |
| weaken the mitigation. | |
| - Advantage: memory layouts don't change, so it's possible for | |
| pointer-authentication-enabled code (for example, in a system library) to | |
| efficiently interoperate with existing code, as long as pointer | |
| authentication can be disabled dynamically. | |
| - Advantage: the size of a signed pointer doesn't grow, which might | |
| significantly increase memory requirements, code size, and register pressure. | |
| - Advantage: the size of a signed pointer is the same as a raw pointer, so | |
| generic APIs which work in types like `void *` (such as `dlsym`) can still | |
| return signed pointers. This means that clients of these APIs will not | |
| require insecure code in order to correctly receive a function pointer. | |
| ### Hashing vs. encrypting pointers | |
| Armv8.3 implements `sign` by computing a cryptographic hash and storing that | |
| in the spare bits of the pointer. This means that there are relatively few | |
| possible values for the valid signed pointer, since the bits corresponding to | |
| the raw pointer are known. Together with an `auth` oracle, this can make it | |
| computationally feasible to discover the correct signature with brute force. | |
| (The implementation should of course endeavor not to introduce `auth` | |
| oracles, but this can be difficult, and attackers can be devious.) | |
| If the implementation can instead *encrypt* the pointer during `sign` and | |
| *decrypt* it during `auth`, this brute-force attack becomes far less | |
| feasible, even with an `auth` oracle. However, there are several problems | |
| with this idea: | |
| - It's unclear whether this kind of encryption is even possible without | |
| increasing the storage size of a signed pointer. If the storage size can be | |
| increased, brute-force attacks can be equally well mitigated by simply storing | |
| a larger signature. | |
| - It would likely be impossible to implement a `strip` operation, which might | |
| make debuggers and other out-of-process tools far more difficult to write, as | |
| well as generally making primitive debugging more challenging. | |
| - Implementations can benefit from being able to extract the raw pointer | |
| immediately from a signed pointer. An Armv8.3 processor executing an | |
| `auth`-and-load instruction can perform the load and `auth` in parallel; | |
| a processor which instead encrypted the pointer would be forced to perform | |
| these operations serially. |