[PATCH v1 1/1] aarch64: make RA signing method extendable for upcoming PAuth_LR
Matthieu Longo
matthieu.longo@arm.com
Wed Nov 13 14:30:02 GMT 2024
On 2024-11-12 22:42, Indu Bhagat wrote:
> On 11/8/24 7:25 AM, Matthieu Longo wrote:
>> The Pointer Authentication (PAuth) architecture provides instructions
>> that enable software to sign an address using SP as a diversifier.
>> This form of PAC instruction is typically used for signing return
>> addresses that are stored on the stack.
>>
>> The new Armv9.5-A architecture introduces an enhancement of the
>> previous PAuth feature, called PAuth_LR. It aims at hardening the PAC
>> in a signed return address. When signing the return address in LR, the
>> PC is used as a diversifier, in addition to the SP to generate PAC code.
>>
>> PAuth introduced the DWARF register RA_SIGN_STATE which stores the
>> return address signed state (currently no signing, or signed with SP).
>> PAuth_LR adds a new value to this state: signed with SP and PC.
>>
>> This DWARF register can now be set via 2 directives:
>> - DW_CFA_AARCH64_negate_ra_state: toggle the RA state in a PAuth
>> context.
>> - DW_CFA_AARCH64_negate_ra_state_with_pc: toggle the RA state in a
>> PAuth_LR context.
>>
>> The existing implementation relies on a binary approach that cannot
>> accomodate with the new state value.
>> This patch is a refactoring to make the RA signing method extendable for
>> the upcoming PAuth_LR patch series.
>
>
> Hi Matthieu,
>
> Thanks for the patch. And apologies for the massive delay. I somehow
> missed this patch in the original series.
No worry. Thanks for having a look.
> It looks like, in theory, an application is allowed to have mixed usage
> of DW_CFA_AARCH64_negate_ra_state_with_pc and
> DW_CFA_AARCH64_negate_ra_state across functions. Can you confirm my
> understanding ?
Yes, in the same compilation unit, a given function is allowed to:
- not use return pointer authentication, and so none of the DWARF
directives mentioned above are emitted.
- use return pointer authentication with SP. The place of signing and
authentication are marked with DW_CFA_AARCH64_negate_ra_state.
- use return pointer authentication with SP + PC. The place of signing
and authentication are marked with DW_CFA_AARCH64_negate_ra_state_with_pc.
The 2 directives cannot be mixed together inside a function body.
> If yes, then technically the signing method information is a per-SFrame-
> FDE information.
Yes.
> I will add supporting Pauth_LR in the SFrame V3 ToDo
What is this "SFrame V3 ToDo" ? I need a bit more context here.
> list. For V3 then, for an SFrame FDE, we would add additional bits to
> keep this information. The SFrame FREs will continue to carry the
> boolean information of ra_mangled_p.
In the binary form, the RA state register is composed of 2 bits, so can
store in theory 4 states but only 3 are valid according to the specs [1].
The boolean ra_mangled_p does not allow to store 3 states, that is why I
had to change it. Adding an additional boolean to know if we are dealing
with SP or SP+PC signing does not seem the right approach from my
perspective as it would make the code more confusing. Please could you
explain me more what you meant ?
> I am a bit curious now to know how will the subsequent patches use the
> refactoring. :)
If we forget about the boiler plates related to the new directive, the
interesting pieces of the future patch interacting with the enum are:
--- a/gas/gen-sframe.h
+++ b/gas/gen-sframe.h
@@ -33,8 +33,9 @@
typedef enum ATTRIBUTE_PACKED
{
- ra_no_signing = 0x0,
- ra_signing_sp = 0x1,
+ ra_no_signing = 0x0,
+ ra_signing_sp = 0x1,
+ ra_signing_sp_pc = 0x3,
} ra_signing_method_t;
--- a/gas/gen-sframe.c
+++ b/gas/gen-sframe.c
@@ -1307,6 +1307,26 @@ sframe_xlate_do_gnu_window_save (struct
sframe_xlate_ctx *xlate_ctx,
return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */
}
+/* Translate DW_CFA_AARCH64_negate_ra_state_with_pc into SFrame context.
+ Return SFRAME_XLATE_OK if success. */
+
+static int
+sframe_xlate_do_aarch64_negate_ra_state_with_pc (struct
sframe_xlate_ctx *xlate_ctx,
+ struct cfi_insn_data
*cfi_insn ATTRIBUTE_UNUSED)
+{
+ struct sframe_row_entry *cur_fre = xlate_ctx->cur_fre;
+
+ gas_assert (cur_fre);
+ /* Toggle the mangled RA status bit. */
+ cur_fre->ra_signing_info
+ = ((cur_fre->ra_signing_info == ra_no_signing)
+ ? ra_signing_sp_pc
+ : ra_no_signing);
+ cur_fre->merge_candidate = false;
+
+ return SFRAME_XLATE_OK;
+}
+
/* Returns the DWARF call frame instruction name or fake CFI name for the
specified CFI opcode, or NULL if the value is not recognized. */
--- a/include/sframe.h
+++ b/include/sframe.h
@@ -265,7 +265,7 @@ typedef struct sframe_fre_info
/* Set the mangled_ra_p bit as indicated. */
#define SFRAME_V1_FRE_INFO_UPDATE_RA_SIGNING_INFO(ra_signing_info,
fre_info) \
- ((((ra_signing_info) & 0x1) << 7) | ((fre_info) & 0x7f))
+ ((((ra_signing_info) & 0x3) << 7) | ((fre_info) & 0x7f))
#define SFRAME_V1_FRE_CFA_BASE_REG_ID(data) ((data) & 0x1)
Please let me know if you have more questions.
Regards,
Matthieu
[1]:
https://github.com/ARM-software/abi-aa/blob/main/aadwarf64/aadwarf64.rst#dwarf-register-names
>> ---
>> gas/gen-sframe.c | 22 ++++++++++++++--------
>> gas/gen-sframe.h | 17 ++++++++++++++---
>> include/sframe.h | 4 ++--
>> 3 files changed, 30 insertions(+), 13 deletions(-)
>>
>> diff --git a/gas/gen-sframe.c b/gas/gen-sframe.c
>> index 626dc33b71d..565148d2842 100644
>> --- a/gas/gen-sframe.c
>> +++ b/gas/gen-sframe.c
>> @@ -254,11 +254,13 @@ static struct sframe_version_ops sframe_ver_ops;
>> static unsigned char
>> sframe_v1_set_fre_info (unsigned int base_reg, unsigned int
>> num_offsets,
>> - unsigned int offset_size, bool mangled_ra_p)
>> + unsigned int offset_size,
>> + ra_signing_method_t ra_signing_info)
>> {
>> unsigned char fre_info;
>> fre_info = SFRAME_V1_FRE_INFO (base_reg, num_offsets, offset_size);
>> - fre_info = SFRAME_V1_FRE_INFO_UPDATE_MANGLED_RA_P (mangled_ra_p,
>> fre_info);
>> + fre_info = SFRAME_V1_FRE_INFO_UPDATE_RA_SIGNING_INFO (ra_signing_info,
>> + fre_info);
>> return fre_info;
>> }
>> @@ -292,10 +294,11 @@ sframe_set_version (uint32_t sframe_version
>> ATTRIBUTE_UNUSED)
>> static unsigned char
>> sframe_set_fre_info (unsigned int base_reg, unsigned int num_offsets,
>> - unsigned int offset_size, bool mangled_ra_p)
>> + unsigned int offset_size,
>> + ra_signing_method_t ra_signing_info)
>> {
>> return sframe_ver_ops.set_fre_info (base_reg, num_offsets,
>> - offset_size, mangled_ra_p);
>> + offset_size, ra_signing_info);
>> }
>> /* SFrame set func info. */
>> @@ -526,7 +529,7 @@ output_sframe_row_entry (symbolS *fde_start_addr,
>> fre_num_offsets = get_fre_num_offsets (sframe_fre);
>> fre_offset_size = sframe_get_fre_offset_size (sframe_fre);
>> fre_info = sframe_set_fre_info (fre_base_reg, fre_num_offsets,
>> - fre_offset_size, sframe_fre->mangled_ra_p);
>> + fre_offset_size, sframe_fre->ra_signing_info);
>> out_one (fre_info);
>> idx = sframe_fre_offset_func_map_index (fre_offset_size);
>> @@ -879,7 +882,7 @@ sframe_row_entry_new (void)
>> fre->merge_candidate = true;
>> /* Reset the mangled RA status bit to zero by default. We will
>> initialize it in
>> sframe_row_entry_initialize () with the sticky bit if set. */
>> - fre->mangled_ra_p = false;
>> + fre->ra_signing_info = ra_no_signing;
>> return fre;
>> }
>> @@ -927,7 +930,7 @@ sframe_row_entry_initialize (struct
>> sframe_row_entry *cur_fre,
>> cur_fre->ra_offset = prev_fre->ra_offset;
>> /* Treat RA mangling as a sticky bit. It retains its value until
>> another
>> .cfi_negate_ra_state is seen. */
>> - cur_fre->mangled_ra_p = prev_fre->mangled_ra_p;
>> + cur_fre->ra_signing_info = prev_fre->ra_signing_info;
>> }
>> /* Return SFrame register name for SP, FP, and RA, or NULL if
>> other. */
>> @@ -1276,7 +1279,10 @@ sframe_xlate_do_aarch64_negate_ra_state (struct
>> sframe_xlate_ctx *xlate_ctx,
>> gas_assert (cur_fre);
>> /* Toggle the mangled RA status bit. */
>> - cur_fre->mangled_ra_p = !cur_fre->mangled_ra_p;
>> + cur_fre->ra_signing_info
>> + = ((cur_fre->ra_signing_info == ra_no_signing)
>> + ? ra_signing_sp
>> + : ra_no_signing);
>> cur_fre->merge_candidate = false;
>> return SFRAME_XLATE_OK;
>> diff --git a/gas/gen-sframe.h b/gas/gen-sframe.h
>> index 8ed46dbb087..35cc01ff0be 100644
>> --- a/gas/gen-sframe.h
>> +++ b/gas/gen-sframe.h
>> @@ -21,11 +21,22 @@
>> #ifndef GENSFRAME_H
>> #define GENSFRAME_H
>> +#include "ansidecl.h"
>> +
>> #define SFRAME_FRE_ELEM_LOC_REG 0
>> #define SFRAME_FRE_ELEM_LOC_STACK 1
>> #define SFRAME_FRE_BASE_REG_INVAL ((unsigned int)-1)
>> +/* Signing method used for return address authentication.
>> + (AArch64 extension) */
>> +
>> +typedef enum ATTRIBUTE_PACKED
>> +{
>> + ra_no_signing = 0x0,
>> + ra_signing_sp = 0x1,
>> +} ra_signing_method_t;
>> +
>> /* SFrame Frame Row Entry (FRE).
>> A frame row entry is a slice of the frame and can be valid for a
>> set of
>> @@ -52,8 +63,8 @@ struct sframe_row_entry
>> on it. */
>> bool merge_candidate;
>> - /* Whether the return address is mangled with pauth code. */
>> - bool mangled_ra_p;
>> + /* Track the PAuth mangling information for RA. */
>> + ra_signing_method_t ra_signing_info;
>
> I think at the FRE level, we will continue to track whether RA is signed
> or not.
>
> But later on, when we add necessary bits in SFrame V3 to convey the
> signing method, we can keep the 'ra_signing_method_t ra_signing_info' in
> the sframe_xlate_ctx object and pass it to SFrame FDE at relevant points.
>
>> /* Track CFA base (architectural) register ID. */
>> unsigned int cfa_base_reg;
>> @@ -146,7 +157,7 @@ struct sframe_version_ops
>> unsigned char format_version; /* SFrame format version. */
>> /* set SFrame FRE info. */
>> unsigned char (*set_fre_info) (unsigned int, unsigned int,
>> unsigned int,
>> - bool);
>> + ra_signing_method_t);
>> /* set SFrame Func info. */
>> unsigned char (*set_func_info) (unsigned int, unsigned int,
>> unsigned int);
>> };
>> diff --git a/include/sframe.h b/include/sframe.h
>> index 347dbc73787..4e7092d0bbe 100644
>> --- a/include/sframe.h
>> +++ b/include/sframe.h
>> @@ -264,8 +264,8 @@ typedef struct sframe_fre_info
>> (((offset_num) & 0xf) << 1) | ((base_reg_id) & 0x1))
>> /* Set the mangled_ra_p bit as indicated. */
>> -#define SFRAME_V1_FRE_INFO_UPDATE_MANGLED_RA_P(mangled_ra_p, fre_info) \
>> - ((((mangled_ra_p) & 0x1) << 7) | ((fre_info) & 0x7f))
>> +#define SFRAME_V1_FRE_INFO_UPDATE_RA_SIGNING_INFO(ra_signing_info,
>> fre_info) \
>> + ((((ra_signing_info) & 0x1) << 7) | ((fre_info) & 0x7f))
>
> We should not remove the SFRAME_V1_FRE_INFO_UPDATE_MANGLED_RA_P definition.
>
> The way I see it is that the signing method has two flavors now:
> - pauth (SP)
> - pauth_lr (SP + PC)
>
> Whether or not RA is mangled is still boolean. Also the signing method
> is chosen per function (correct ?), so we simply need to keep this
> information per SFrame FDE.
>
> Additionally, removing a macro may cause a consumer program of libsframe
> to run into build time failures.
>
>> #define SFRAME_V1_FRE_CFA_BASE_REG_ID(data) ((data) & 0x1)
>> #define SFRAME_V1_FRE_OFFSET_COUNT(data) (((data) >> 1) & 0xf)
>
More information about the Binutils
mailing list