You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[PAUTHABIELF64] Introduce R_AARCH64_AUTH_TLSDESC_CALL and .tlsdescauthcall #393
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:
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:
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:
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.
.tlsdescauthcall — an assembler directive that emits this relocation, analogous to .tlsdesccall.
@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?
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.
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.
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.
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.
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.
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.
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 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:
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
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.
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".
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"
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).
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?
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.
Activity
kovdan01 commentedon May 11, 2026
Tagging @smithp35 @asl @jrtc27 @atrosinenko
asl commentedon May 11, 2026
@kovdan01 I guess main question here is: do we need a separate relocation for this? Can we use
R_AARCH64_TLSDESC_CALLonbrlaafor the same purpose? What are pros and cons of this approach?smithp35 commentedon May 11, 2026
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_CALLhttps://github.com/llvm/llvm-project/blob/main/lld/ELF/Arch/AArch64.cpp#L350If 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, theR_AARCH64_TLSDESC_CALLnever appears without the other 3 TLSDESC relocations, which should setNEEDS_TLSDESC_NONAUTHif they are not the AUTH variants.I could be missing something though.
kovdan01 commentedon May 12, 2026
@asl @smithp35 Thanks for your feedback!
Initially, I've indeed tried to reuse
R_AARCH64_TLSDESC_CALLin lld, but my initial attempt ended up in the following results and conclusions.I was unable to easily make lld use
R_AARCH64_TLSDESC_CALLfor 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.My understanding was that use of
R_AARCH64_TLSDESC_CALLin 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_CALLfor 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-relaxationssmithp35 commentedon May 12, 2026
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.
jrtc27 commentedon May 12, 2026
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.
kovdan01 commentedon May 12, 2026
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
.tlsdescannotation for AUTH TLSDESCblraacall, we can have two situations on lld side: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_CALLas "we need non-AUTH TLSDESC", and trying to have both AUTH and non-AUTH TLSDESC entries for one symbol is not supported by lld.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
smithp35 commentedon May 13, 2026
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_CALLwould behave identically toR_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 Xdat 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 relaxR_AARCH64_AUTH_TLSDESC_CALLwithout having to record the state from otherR_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 Xdis at the end of the sequence the linker will usually seeR_AARCH64_AUTH_TLSDESCafter the previousR_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 reusedR_AARCH64_TLSDESC_CALLbefore any of theR_AARCH64_AUTH_TLSDESCrelocations so we could get an incorrect relaxation ofblr Xdtonop.We can avoid that edge case, and stick to the principle of the relocation being self-contained.
Cons:
For me I think the Pros make the case for
.tlsdescauthcallandR_AARCH64_AUTH_TLSDESC_CALL. Apologies for changing my mind.I'll make a draft ABI tomorrow so we can discuss there.
smithp35 commentedon May 14, 2026
PR for new relocation #395
kovdan01 commentedon May 18, 2026
Thanks @smithp35!
Just for transparency - PRs implementing support in llvm-project:
jrtc27 commentedon May 22, 2026
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 derivationpointer 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".
kovdan01 commentedon May 22, 2026
@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:
jrtc27 commentedon May 27, 2026
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?
smithp35 commentedon May 27, 2026
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.
[pauthabielf64] Define R_AARCH64_AUTH_TLSDESC_CALL. (#395)