Skip to content

[PAUTHABIELF64] Introduce R_AARCH64_AUTH_TLSDESC_CALL and .tlsdescauthcall #393

Closed

Description

@kovdan01

Background

The PAUTHABIELF64 document currently specifies the following relocations for the AUTH TLSDESC sequence:

  • R_AARCH64_AUTH_TLSDESC_ADR_PAGE21
  • R_AARCH64_AUTH_TLSDESC_LD64_LO12
  • R_AARCH64_AUTH_TLSDESC_ADD_LO12

The full AUTH TLSDESC call sequence is:

  adrp  x0, :tlsdesc_auth:var              // R_AARCH64_AUTH_TLSDESC_ADR_PAGE21
  ldr   x16, [x0, #:tlsdesc_auth_lo12:var] // R_AARCH64_AUTH_TLSDESC_LD64_LO12
  add   x0, x0, #:tlsdesc_auth_lo12:var    // R_AARCH64_AUTH_TLSDESC_ADD_LO12
  blraa x16, x0
  // TPIDR_EL0 offset now in x0

Note that blraa is required here because the AUTH TLSDESC handler pointer is stored in a signed GOT entry using the IA key.

Problem

For non-auth TLSDESC, the R_AARCH64_TLSDESC_CALL relocation (emitted via the .tlsdesccall assembler directive) annotates the blr instruction, allowing the linker to locate it for relaxation:

  adrp  x0, :tlsdesc:var             // R_AARCH64_TLSDESC_ADR_PAGE21
  ldr   x1, [x0, #:tlsdesc_lo12:var] // R_AARCH64_TLSDESC_LD64_LO12
  add   x0, x0, #:tlsdesc_lo12:var   // R_AARCH64_TLSDESC_ADD_LO12
  .tlsdesccall var                   // R_AARCH64_TLSDESC_CALL
  blr   x1

No equivalent annotation relocation exists for the AUTH TLSDESC sequence. Without it, the linker cannot locate the blraa instruction to perform relaxation.

Motivation

For undefined weak non-preemptible symbols, the linker knows at link time that the symbol resolves to NULL. In this case, the entire AUTH TLSDESC sequence can be relaxed:

  // Before (AUTH TLSDESC):
  adrp  x0, :tlsdesc_auth:var              // R_AARCH64_AUTH_TLSDESC_ADR_PAGE21
  ldr   x16, [x0, #:tlsdesc_auth_lo12:var] // R_AARCH64_AUTH_TLSDESC_LD64_LO12
  add   x0, x0, #:tlsdesc_auth_lo12:var    // R_AARCH64_AUTH_TLSDESC_ADD_LO12
  .tlsdescauthcall var                     // R_AARCH64_AUTH_TLSDESC_CALL
  blraa x16, x0

  // After (relaxed):
  mov   x0, #0x0
  nop
  nop
  nop

This is the only relaxation case applicable to AUTH TLSDESC. Broader relaxations (such as GD->IE or GD->LE with non-statically-known-NULL symbols) are not possible because pointer authentication requires signing at program start-up.

Proposal

Introduce:

  1. R_AARCH64_AUTH_TLSDESC_CALL — a relocation that annotates the blraa instruction in an AUTH TLSDESC call sequence, analogous to R_AARCH64_TLSDESC_CALL for the non-auth case.
  2. .tlsdescauthcall — an assembler directive that emits this relocation, analogous to .tlsdesccall.

See also llvm/llvm-project#173296 tracking progress on related issue.

Activity

  1. kovdan01 commented on May 11, 2026

    @kovdan01
    Author
  2. asl commented on May 11, 2026

    @asl

    @kovdan01 I guess main question here is: do we need a separate relocation for this? Can we use R_AARCH64_TLSDESC_CALL on brlaa for the same purpose? What are pros and cons of this approach?

  3. smithp35 commented on May 11, 2026

    @smithp35
    Contributor

    Thanks for raising. I'll take a deeper look tomorrow.

    At the specification level it seems like it should be possible to reuse R_AARCH64_TLSDESC_CALL, and rely on the other 3 Relocations in the sequence to determine if it is AUTH or NOT.

    On a quick scan, in the current implementation of LLD, R_AARCH64_TLSDESC_CALL https://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/AArch64.cpp#L350

        case R_AARCH64_TLSDESC_CALL:
          sym.setFlags(NEEDS_TLSDESC_NONAUTH);
          if (!ctx.arg.shared)
            sec.addReloc({R_TPREL, type, offset, addend, &sym});
          continue;
    

    If there is a supported relaxation for AUTH TLSDESC, then it looks like it would be possible to remove the sym.setFlags(NEEDS_TLSDESC_NONAUTH);. Possibly illegal assembly files aside, the R_AARCH64_TLSDESC_CALL never appears without the other 3 TLSDESC relocations, which should set NEEDS_TLSDESC_NONAUTH if they are not the AUTH variants.

    I could be missing something though.

  4. kovdan01 commented on May 12, 2026

    @kovdan01
    Author

    @asl @smithp35 Thanks for your feedback!

    Initially, I've indeed tried to reuse R_AARCH64_TLSDESC_CALL in lld, but my initial attempt ended up in the following results and conclusions.

    1. I was unable to easily make lld use R_AARCH64_TLSDESC_CALL for both AUTH and non-AUTH TLSDESC - the logic was explicitly assuming that this relocation only appears for non-AUTH TLSDESC, and I was initially unable to quickly get rid of this assumption.

    2. My understanding was that use of R_AARCH64_TLSDESC_CALL in AUTH context might be a bit confusing since every other TLSDESC relocation for AUTH case has the corresponding prefix.

    Fortunately, my second attempt of lld implementation with existing reloc re-used was successful, and it seems that we can more or less elegantly support re-use of R_AARCH64_TLSDESC_CALL for AUTH TLSDESC in lld. I've updated the PR llvm/llvm-project#194636 accordingly.

    So, my understanding of @smithp35 's point is as follows. When choosing between: (a) introducing additional AUTH entities mirroring non-AUTH ones for keeping things in AUTH world naming consistent; (b) reusing existing entities when we can, even if AUTH naming becomes inconsistent - we choose (b).

    @smithp35 Please let me know if my understanding is correct. If so, I'll undraft my PR and consider this ticket not needed.

    Sorry for any confusion caused by this ticket. I'll leave it opened until merge of llvm/llvm-project#194636 just in case, but I definitely do not mind if you prefer to close it.

    Also, if we decide to reuse R_AARCH64_TLSDESC_CALL, probably we can still leave a corresponding note about linker relaxation of AUTH TLSDESC call sequences for non-preemptible undefined weak symbols in PAUTHABIELF64 document. See, for example, TLSDESC relaxations described in SysV ABI document https://github.com/ARM-software/abi-aa/blob/2025Q4/sysvabi64/sysvabi64.rst#static-link-time-tls-relaxations

  5. smithp35 commented on May 12, 2026

    @smithp35
    Contributor

    I'll get an answer tomorrow. Will check the existing language in aaelf64 for R_AARCH64_TLSDESC_CALL and the BFD implementation to see if there's any reason why we can't reuse it.

    I'm leaning towards reuse as the transformation is the same in both cases, and it would avoid needing a new assembler directive.

  6. jrtc27 commented on May 12, 2026

    @jrtc27
    Contributor

    I haven’t yet had a chance to look through the spec, but the main risk from reuse in my view would be that an old linker would misinterpret the pauth tlsdesc as being normal tlsdesc and relocate it inappropriately as a result.

  7. kovdan01 commented on May 12, 2026

    @kovdan01
    Author

    I haven’t yet had a chance to look through the spec, but the main risk from reuse in my view would be that an old linker would misinterpret the pauth tlsdesc as being normal tlsdesc and relocate it inappropriately as a result.

    I'm not sure if we can actually expect to face such an issue. To the best of my knowledge, lld is the only linker now supporting AUTH relocations. Assuming that we now teach llvm backend to emit .tlsdesc annotation for AUTH TLSDESC blraa call, we can have two situations on lld side:

    1. Old lld (before [PAC][lld] Do not emit AUTH relocs against undef weak non-preemptible symbols llvm/llvm-project#194636). It would error out because it would treat R_AARCH64_TLSDESC_CALL as "we need non-AUTH TLSDESC", and trying to have both AUTH and non-AUTH TLSDESC entries for one symbol is not supported by lld.

    2. New lld (with [PAC][lld] Do not emit AUTH relocs against undef weak non-preemptible symbols llvm/llvm-project#194636). It would just handle things properly.

    Other linkers which are now not supporting AUTH relocs would be expected to implement proper support from the very beginning (when/if they decide to do so).

    But it's just my vision and I might be missing smth. @jrtc27 Please let me know if you have some other risky case in your mind

  8. smithp35 commented on May 13, 2026

    @smithp35
    Contributor

    Reading through the PR in more detail, to think through the pros and cons of introducing a new relocation type. I had initially thought that R_AARCH64_AUTH_TLSDESC_CALL would behave identically to R_AARCH64_TLSDESC_CALL, but the conditions for when the relaxation can take place are different, even though the output is different.

    Pros:

    The conditions for when a blx Xd at the end of a TLSDESC sequence can be relaxed are different for Auth and non-Auth variants. If there are separate relocations then the static linker can handle the logic for whether it can relax R_AARCH64_AUTH_TLSDESC_CALL without having to record the state from other R_AARCH64_AUTH_TLSDESC_* relocations in the sequence. A general principle that we've applied to Arm and AArch64 ELF is that each relocation should be self-contained to be resolved, and not need to rely on the results of other relocations.

    In practice, as the blr Xd is at the end of the sequence the linker will usually see R_AARCH64_AUTH_TLSDESC after the previous R_AARCH64_AUTH_TLSDESC_* relocations so flagging the symbol as being from an auth sequence is usually easy. However ELF doesn't guarantee the order of relocations so in an extreme case we could see a reused R_AARCH64_TLSDESC_CALL before any of the R_AARCH64_AUTH_TLSDESC relocations so we could get an incorrect relaxation of blr Xd to nop.

    We can avoid that edge case, and stick to the principle of the relocation being self-contained.

    Cons:

    • Additional relocation directive and assembler directive needed.

    For me I think the Pros make the case for .tlsdescauthcall and R_AARCH64_AUTH_TLSDESC_CALL. Apologies for changing my mind.

    I'll make a draft ABI tomorrow so we can discuss there.

  9. smithp35 commented on May 14, 2026

    @smithp35
    Contributor

    PR for new relocation #395

  10. kovdan01 commented on May 18, 2026

    @kovdan01
    Author
  11. jrtc27 commented on May 22, 2026

    @jrtc27
    Contributor

    Back home from my travels, reunited with my laptop and catching up on review requests. If I understand correctly, you don't just want the resolver function pointer to be signed to protect control flow, you also want the TLS data pointer to be signed? And you want that to be per TLS object, not shared for the entire (static or per-dlopened-object dynamic) TLS block? Therefore the classic "TP + (static/dynamic) offset" model for LE/IE doesn't work as the pointer provenance is always from TP so carries its signature? Is the resolver still returning the offset to add to TP (which will presumably include high bits being set to cause the final arithmetic result to give the right signature there for the object, similar to how for classic unsigned TLS you get crazy large offsets for dynamic TLS blocks as they're different allocations to the static block pointed to by TP)? If all this is true, it feels a lot like you're getting close to the same problems faced by CHERI, where you need capability derivation pointer signing to be performed at program / thread startup, not dynamically on every TLS access, and the indirect ("TGOT-based") TLS model we're adopting there might also be rather helpful here, as you'd still be able to do LE/IE-style TLS rather than calling a resolver, by loading a pointer from your TGOT. But that's a bigger question for optimising this, which I imagine people will care about doing in time.

    Is any of this written down somewhere? IIRC the base AArch64 TLSDESC is not well documented, but given this is doing novel things with pointer signing I think it would be worth writing down what exactly is going on here, as it's not obvious what's changed beyond slapping "auth" on everything and using a "blraa" instead of a "blr".

  12. kovdan01 commented on May 22, 2026

    @kovdan01
    Author

    I think it would be worth writing down what exactly is going on here, as it's not obvious what's changed beyond slapping "auth" on everything and using a "blraa" instead of a "blr"

    @jrtc27 I'm definitely not in a position to answer for @smithp35 , but my understanding is that "Appendix Signed GOT" section of pauthabielf64 document should cover this: https://github.com/ARM-software/abi-aa/blob/2025Q4/pauthabielf64/pauthabielf64.rst#appendix-signed-got

    By "covers" I mean that it covers how GOT entries are signed, and, given that TLSDESC relies on having two GOT entries, knowing how signed GOT works gives us knowledge about how signed TLSDESC works.

    I actually agree that it might deserve explicit description in docs (like, how this signed GOT description applies to signed TLSDESC).

    Also, section "5.3 General Restrictions" contains this https://github.com/ARM-software/abi-aa/blob/2025Q4/pauthabielf64/pauthabielf64.rst#general-restrictions:

    PAUTHELF64 only supports the descriptor based TLS (TLSDESC).

  13. jrtc27 commented on May 27, 2026

    @jrtc27
    Contributor

    That's for the pointers in the GOT, but I don't think that says anything about the pointers that come out of the resolver, which are presumably why you can't relax to IE/LE?

  14. smithp35 commented on May 27, 2026

    @smithp35
    Contributor

    The original intent for the PAuthABI ELF document was to be a toolkit for a platform to build their ABI and signing schema on top of that.

    Looking at the implementation in MUSL https://github.com/access-softek/musl/blob/v1.2.5-pauth-rev2025-11-21/ldso/dynlink.c#L589 it looks like the both the pointer to the resolver and its data are signed. For reference the resolvers are currently defined in https://github.com/access-softek/musl/blob/v1.2.5-pauth-rev2025-11-21/src/ldso/aarch64/tlsdesc.S

    Looking at TLSDESC, I agree that there should be a possible implementation that only signs the code-pointer to the resolver function. Leaving the value, which is often an offset from the thread-pointer unsigned. That in principle should allow some relaxation to initial-exec or local-exec to occur. There's a trade-off in doing that, but a platform could make that choice.

    We do have a design documents section where rationale and other non-normative information can live. I could write up something there.

  15. added a commit that references this issue on Jul 6, 2026
    6e0d661
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions