[PATCH v1 1/1] aarch64: make RA signing method extendable for upcoming PAuth_LR
Indu Bhagat
indu.bhagat@oracle.com
Sun Dec 1 01:28:26 GMT 2024
On 11/25/24 6:21 AM, Matthieu Longo wrote:
> On 2024-11-14 04:36, Indu Bhagat wrote:
>> 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.
>
> Firstly, I apologize for the confusion on my side. I thought SFrame was
> the same thing as DWARF.
> I had a look at the spec v2, everything makes sense now.
> Great presentation by the way :) => https://www.youtube.com/watch?
> v=4XrFYpjyodo
>
> I am going to publish a different patch series to add the support for
> the new CFI directive in both DWARF and SFrame (warning + no FDE
> generatation until SFrame v3 is released).
>
> Regarding SFrame v3, do you have a draft version somewhere ?
> https://sourceware.org/binutils/wiki/sframe does not list any.
>
Unfortunately, not yet. It needs to be work on a bit and then posted.
> Let me rephrase to check if I understood well your proposition for v3.
> Since PAuth_LR is per-SFrame SDE, you would like to store whether the
> address is signed with PAuth or PAuth_LR inside the 2 unused bits of
> struct sframe_func_desc_entry. Or is it somewhere else ?
>
Yes, Whether it is PAuth / PAuth_LR can encoded in struct
sframe_func_desc_entry (SFrame FDE).
> PAuth_LR also requires to know where the signing occured (PC of the
> signing instruction) to demangle the return address.
> Is it the responsibility of stacktrace generator to figure out where
> this signing happens by scanning the instructions from the start of the
> function looking for either "pacm + paci<k>sp" or "paci<k>sppc" ?
>
> In the DWARF unwinder in libunwind (the patch series has not been
> published yet, it is still in-progress, sorry for that), the demangling
> of the address relies on the address value matching the DWARF
> instruction DW_CFA_AARCH64_negate_ra_state_with_pc and substracts 4
> (size of an instruction on AArch64) from it.
>
> In my understanding, this search for the signing instruction would go
> against the original goal of SFrame consisting in providing a quick and
> simple stacktracing mechanism only relying on the minimal amount of
> informations stored in an FRE.
> Would it make sense to store an additional information (the address of
> signing) in the FRE for this use-case ?
>
Yes, exactly, since the RA-signing information can change at the
granularity of instruction addresses, this information (bool) can go in
SFrame Frame Row Entry (FRE).
>>>> 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