[PATCH v1 1/1] aarch64: make RA signing method extendable for upcoming PAuth_LR
Indu Bhagat
indu.bhagat@oracle.com
Thu Nov 14 04:36:35 GMT 2024
On 11/13/24 6:30 AM, Matthieu Longo wrote:
> 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.
>
Thanks for confirming.
>> I will add supporting Pauth_LR in the SFrame V3 ToDo
>
> What is this "SFrame V3 ToDo" ? I need a bit more context here.
>
SFrame (Simple Frame stack trace) format is currently at version 2.
There are some planned enhancements to be targeted for Version 3. I
have been keeping a rough ToDO list, I still need to clean it up and
publish it somehwere on the Binutils wiki.
IOW, it means SFrame V3 is being planned.
>> 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 ?
>
RA state register is a DWARF pseudo register. In SFrame, we can chose
to keep information differently:
- A function level bit to indicate whether pauth or pauth_lr is in
effect. Something similar to the current SFRAME_V1_FUNC_PAUTH_KEY.
- Continue to use 1-bit in SFrame FRE to indicate whether RA is mangled.
Regarding it being confusing, I think this can be addressed by providing
access macros/APIs.
In any case, the above suggestion cannot be done unless we bump the
format version to version 3.
For the immediate problem at hand around handling
DW_CFA_AARCH64_negate_ra_state_with_pc in SFrame, I suggest we error out
with a warning. See below.
>> 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;
> +
Till we are emitting SFrame V2, this function will need to just return
SFRAME_XLATE_ERR_NOTREPRESENTED together after emitting a warning.
Perhaps something like:
as_warn (_("skipping SFrame FDE; .cfi_negate_ra_state_with_pc"));
return SFRAME_XLATE_ERR_NOTREPRESENTED; /* Not represented. */
> + 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))
>
fre_info is uint8_t. We do not have space to keep more than 1 + 7 bits
of information here.
> #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