Skip to content

[AArch64] Add error handling for unsupported bases in lowerConstantPtrAuth#189474

Merged
oskarwirga merged 7 commits intollvm:mainfrom
oskarwirga:arm64e_asmprinter
Apr 8, 2026
Merged

[AArch64] Add error handling for unsupported bases in lowerConstantPtrAuth#189474
oskarwirga merged 7 commits intollvm:mainfrom
oskarwirga:arm64e_asmprinter

Conversation

@oskarwirga
Copy link
Copy Markdown
Contributor

@oskarwirga oskarwirga commented Mar 30, 2026

This is part of work being done in #188378 and #188638, split out from #188650.

lowerConstantPtrAuth silently miscompiled ConstantPtrAuth constants with non-GlobalValue pointer bases — emitting 0@AUTH(da,0) instead of erroring.

Changes:

  • Handle ConstantPointerNull bases explicitly
  • Error via reportFatalUsageError on any remaining unresolved base (e.g. nested ptrauth) instead of silently miscompiling

This PR was mostly developed with LLM assistance

Enable LookThroughIntToPtr on stripAndAccumulateConstantOffsets so
that Swift's inttoptr(add(ptrtoint(@global), offset)) pattern inside
ptrauth constants is handled correctly. Without this, the base global
is not resolved and the constant is emitted as a bare integer.
@llvmbot
Copy link
Copy Markdown
Member

llvmbot commented Mar 30, 2026

@llvm/pr-subscribers-backend-aarch64

Author: Oskar Wirga (oskarwirga)

Changes

This is part of work being done in #188378 and #188638, split out from #188650.

Swift generates inttoptr(add(ptrtoint(@<!-- -->global), offset)) inside ptrauth constants. stripAndAccumulateConstantOffsets already supports this pattern via its LookThroughIntToPtr parameter, but lowerConstantPtrAuth wasn't enabling it. Without this, the base global is not resolved and the constant is emitted as 0@<!-- -->AUTH(da,0) instead of (global+offset)@<!-- -->AUTH(da,0), leading to the program crashing.

This PR was mostly developed with LLM assistance, but human tested on arm64e hardware.


Full diff: https://github.com/llvm/llvm-project/pull/189474.diff

2 Files Affected:

  • (modified) llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp (+5-1)
  • (modified) llvm/test/CodeGen/AArch64/ptrauth-reloc.ll (+14)
diff --git a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
index 2b8db27599d3c..47281791ff7a0 100644
--- a/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
+++ b/llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp
@@ -2680,9 +2680,13 @@ AArch64AsmPrinter::lowerConstantPtrAuth(const ConstantPtrAuth &CPA) {
   MCContext &Ctx = OutContext;
 
   // Figure out the base symbol and the addend, if any.
+  // LookThroughIntToPtr handles Swift patterns like:
+  //   inttoptr (i64 add (i64 ptrtoint (ptr @global to i64), i64 2) to ptr)
   APInt Offset(64, 0);
   const Value *BaseGV = CPA.getPointer()->stripAndAccumulateConstantOffsets(
-      getDataLayout(), Offset, /*AllowNonInbounds=*/true);
+      getDataLayout(), Offset, /*AllowNonInbounds=*/true,
+      /*AllowInvariantGroup=*/false, /*ExternalAnalysis=*/nullptr,
+      /*LookThroughIntToPtr=*/true);
 
   auto *BaseGVB = dyn_cast<GlobalValue>(BaseGV);
 
diff --git a/llvm/test/CodeGen/AArch64/ptrauth-reloc.ll b/llvm/test/CodeGen/AArch64/ptrauth-reloc.ll
index 14f5571fc2deb..befb726bcdab8 100644
--- a/llvm/test/CodeGen/AArch64/ptrauth-reloc.ll
+++ b/llvm/test/CodeGen/AArch64/ptrauth-reloc.ll
@@ -113,6 +113,20 @@
 
 @g.weird_ref.da.0 = constant i64 ptrtoint (ptr inttoptr (i64 ptrtoint (ptr ptrauth (ptr getelementptr (i8, ptr @g, i64 16), i32 2) to i64) to ptr) to i64)
 
+; Swift generates inttoptr(add(ptrtoint(@global), offset)) inside ptrauth.
+
+; CHECK-ELF-LABEL:     .globl g.inttoptr_add.da.0
+; CHECK-ELF-NEXT:      .p2align 3
+; CHECK-ELF-NEXT:    g.inttoptr_add.da.0:
+; CHECK-ELF-NEXT:      .xword (g+2)@AUTH(da,0)
+
+; CHECK-MACHO-LABEL:   .globl _g.inttoptr_add.da.0
+; CHECK-MACHO-NEXT:    .p2align 3
+; CHECK-MACHO-NEXT:  _g.inttoptr_add.da.0:
+; CHECK-MACHO-NEXT:    .quad (_g+2)@AUTH(da,0)
+
+@g.inttoptr_add.da.0 = constant ptr ptrauth (ptr inttoptr (i64 add (i64 ptrtoint (ptr @g to i64), i64 2) to ptr), i32 2)
+
 ; CHECK-ELF-LABEL:     .globl g_weak.ref.ia.42
 ; CHECK-ELF-NEXT:      .p2align 3
 ; CHECK-ELF-NEXT:    g_weak.ref.ia.42:

Copy link
Copy Markdown
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an appropriate reportFatalUsageError() call for the case where the dyn_cast<GlobalValue>() fails. I'm concerned that we're going to find more forms of constant expressions getting miscompiled.

Enable LookThroughIntToPtr on stripAndAccumulateConstantOffsets so
that Swift's inttoptr(add(ptrtoint(@global), offset)) pattern inside
ptrauth constants is handled correctly. Without this, the base global
is not resolved and the constant is miscompiled.

Also replace the silent fallback for unresolved base values with
reportFatalUsageError to catch future unsupported constant expression
forms.
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 30, 2026

🐧 Linux x64 Test Results

  • 193175 tests passed
  • 5009 tests skipped

✅ The build succeeded and all tests passed.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 30, 2026

🪟 Windows x64 Test Results

  • 133132 tests passed
  • 3069 tests skipped

✅ The build succeeded and all tests passed.

ptrauth(ptr null, ...) is legitimate — the base is a
ConstantPointerNull, not a failed resolution. Only fire
reportFatalUsageError when stripAndAccumulateConstantOffsets
leaves an unresolved ConstantExpr.

Fixes ptrauth-irelative.ll test failure.
Comment thread llvm/lib/Target/AArch64/AArch64AsmPrinter.cpp Outdated
Check for known-safe types (ConstantPointerNull) and error on
everything else, rather than checking for known-bad types
(ConstantExpr). This catches other unhandled forms like nested
ptrauth constants.

Adds test cases for null pointer ptrauth and unsupported base
expression error.
@asl
Copy link
Copy Markdown
Collaborator

asl commented Mar 30, 2026

leading to the program crashing.

How does this work in Swift then? Is there downstream change? Can you point to it?

@asl asl requested a review from ahmedbougacha March 30, 2026 23:57
@oskarwirga
Copy link
Copy Markdown
Contributor Author

How does this work in Swift then? Is there downstream change? Can you point to it?

Swift's fork does not contain this fix. I encountered this when lowerConstantPtrAuth received a ConstantPtrAuth with a null base and with an inttoptr(add(ptrtoint)) pattern. The test case in ptrauth-reloc.ll is derived from the crashing IR.

I've updated the PR description's wording to be more precise.

Copy link
Copy Markdown
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but wait a bit to see if the ptrauth reviewers have additional comments.

@ojhunt
Copy link
Copy Markdown
Contributor

ojhunt commented Mar 31, 2026

@ahmedbougacha I don't think this should be merged until you've looked at this.

@kovdan01
Copy link
Copy Markdown
Contributor

kovdan01 commented Apr 3, 2026

@oskarwirga Just being curious: could you please share a minimal swift reproducer (with compiler invocation command which you use) which results in such a pattern which needs special handling?

@ojhunt
Copy link
Copy Markdown
Contributor

ojhunt commented Apr 4, 2026

@rjmccall would you mind looking at the mentioned swift codegen issue? Is that some idiosyncrasy we don't normally generate or is there something important we've missed in upstreaming?

@ojhunt
Copy link
Copy Markdown
Contributor

ojhunt commented Apr 4, 2026

@rjmccall would you mind looking at the mentioned swift codegen issue? Is that some idiosyncrasy we don't normally generate or is there something important we've missed in upstreaming?

ignore me - I thought this was one of the other PRs (one of the codegen ones), this one is asm printing and I assume we just don't have existing tests

The inttoptr(add(ptrtoint)) pattern is not Swift-specific.
Drop the attribution and just describe the pattern.
@oskarwirga
Copy link
Copy Markdown
Contributor Author

@oskarwirga Just being curious: could you please share a minimal swift reproducer (with compiler invocation command which you use) which results in such a pattern which needs special handling?

After some more investigation, this was indeed a non-standard application of compiler flags/custom toolchain. I encountered this inttoptr(add(ptrtoint)) pattern when compiling some swift with -ptrauth-emit-wrapper-globals=false which emits ConstantPtrAuth instead of relying on the llvm.ptrauth section. That said, you can reproduce the miscompilation just with LLC:

@g = external global i32
@null_signed = constant ptr ptrauth (ptr null, i32 2)
@offset_signed = constant ptr ptrauth (ptr inttoptr (i64 add (i64 ptrtoint (ptr @g to i64), i64 2) to ptr), i32 2)
llc -mtriple arm64e-apple-darwin -o - repro.ll

@offset_signed emits (0)@AUTH(da,0) instead of the correct (_g+2)@AUTH(da,0).

I've pushed a commit to remove the Swift blame (sorry Swift!) and updated the description to suit.

@efriedma-quic
Copy link
Copy Markdown
Collaborator

If we don't need it, I'd rather not set LookThroughIntToPtr to true, I think? (The other change should ensure we don't miscompile.)

@oskarwirga
Copy link
Copy Markdown
Contributor Author

If we don't need it, I'd rather not set LookThroughIntToPtr to true, I think? (The other change should ensure we don't miscompile.)

I see both sides to this. I lean towards supporting this pattern mainly because the parameter already exists and its trivial to support it but since we don't have default cases leading to this pattern we'd be widening what lowerConstantPtrAuth needs to support.

inttoptr(add(ptrtoint(@global), offset)) is valid IR but it isn't generated by default so I don't know if this path needs to support it.

@atrosinenko
Copy link
Copy Markdown
Contributor

According to the documentation of stripAndAccumulateConstantOffsets

  /// If \p LookThroughIntToPtr is true then this method also looks through
  /// IntToPtr and PtrToInt constant expressions. The returned pointer may not
  /// have the same provenance as this value.

I'm not too experienced with all that pointer provenance stuff - on one hand, we are at the very end of the pipeline at this point, and there is no LLVM IR consumers past it. On the other hand, having pointers computed with plain add instead of proper pointer arithmetic may possibly indicate some issue, I guess. For a simple C source

int storage[10];

int * __ptrauth(2, 0, 0) result = storage + 2;

the following LLVM IR is produced by ./bin/clang -target aarch64-linux-pauthtest -S -o- example.c (note that getelementptr is generated):

@storage = dso_local global [10 x i32] zeroinitializer, align 4
@result = dso_local global ptr ptrauth (ptr getelementptr (i8, ptr @storage, i64 8), i32 2), align 8
Full output
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
target triple = "aarch64-unknown-linux-pauthtest"

@storage = dso_local global [10 x i32] zeroinitializer, align 4
@result = dso_local global ptr ptrauth (ptr getelementptr (i8, ptr @storage, i64 8), i32 2), align 8

!llvm.module.flags = !{!0, !1, !2, !3, !4, !5, !6, !7}
!llvm.ident = !{!8}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 8, !"ptrauth-sign-personality", i32 1}
!2 = !{i32 1, !"aarch64-elf-pauthabi-platform", i32 268435458}
!3 = !{i32 1, !"aarch64-elf-pauthabi-version", i32 1791}
!4 = !{i32 8, !"PIC Level", i32 2}
!5 = !{i32 7, !"PIE Level", i32 2}
!6 = !{i32 7, !"uwtable", i32 2}
!7 = !{i32 7, !"frame-pointer", i32 4}
!8 = !{!"clang version 23.0.0git ([email protected]:llvm/llvm-project.git 19c862dd277b386bffb18247d53c801ae441390b)"}

The assembly output is as follows (both with this PR and on the mainline):

result:
        .xword  (storage+8)@AUTH(da,0)
        .size   result, 8
Full output
        .aeabi_subsection       aeabi_pauthabi, required, uleb128
        .aeabi_attribute        1, 268435458    // Tag_PAuth_Platform
        .aeabi_attribute        2, 1791 // Tag_PAuth_Schema
        .section        .note.gnu.property,"a",@note
        .p2align        3, 0x0
        .word   4
        .word   24
        .word   5
        .asciz  "GNU"
        .word   3221225473
        .word   16
        .xword  268435458
        .xword  1791
.Lsec_end0:
        .text
        .file   "test.c"
        .type   storage,@object                 // @storage
        .bss
        .globl  storage
        .p2align        2, 0x0
storage:
        .zero   40
        .size   storage, 40

        .type   result,@object                  // @result
        .data
        .globl  result
        .p2align        3, 0x0
result:
        .xword  (storage+8)@AUTH(da,0)
        .size   result, 8

        .ident  "clang version 23.0.0git ([email protected]:llvm/llvm-project.git 19c862dd277b386bffb18247d53c801ae441390b)"
        .section        ".note.GNU-stack","",@progbits
        .addrsig
        .addrsig_sym storage

Replacing the definition of @result with

@result = dso_local global ptr ptrauth (ptr getelementptr (i8, ptr null, i64 8), i32 2), align 8

changes .xword (storage+8)@AUTH(da,0) to .xword (8)@AUTH(da,0) (both with this PR and in the mainline). Furthermore, changing the definition of @result to

@result = dso_local global ptr ptrauth (ptr null, i32 2), align 8

turns the assembly output into .xword (0)@AUTH(da,0) in both versions.

Copy link
Copy Markdown
Contributor

@atrosinenko atrosinenko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The patch looks mostly good to me, except that I'm not sure we should actually enable LookThroughIntToPtr "just in case" (i.e. if it is not actually emitted by Swift, for example). But explicitly checking that we finally got null base instead of just assuming "anything unknown is null" is definitely important.

Considering the description, the mainline seems not to crash on ptr null bases, but an actual issue fixed by this PR seems to be even worse, as whether we would like to support the inttoptr(add(ptrtoint)) pattern or not, we definitely should not silently miscompile anything.

Per reviewer feedback: no default frontend generates the
inttoptr(add(ptrtoint)) pattern inside ConstantPtrAuth, so don't
widen what lowerConstantPtrAuth accepts. The reportFatalUsageError
catch-all ensures any unhandled pattern errors instead of silently
miscompiling.
@oskarwirga
Copy link
Copy Markdown
Contributor Author

The patch looks mostly good to me, except that I'm not sure we should actually enable LookThroughIntToPtr "just in case" (i.e. if it is not actually emitted by Swift, for example). But explicitly checking that we finally got null base instead of just assuming "anything unknown is null" is definitely important.

Sounds good to me, thanks for diving into it :) I've updated the PR + desc to drop LookThroughIntToPtr but kept the explicit null-handling.

@oskarwirga oskarwirga changed the title [AArch64] Handle inttoptr+add+ptrtoint pattern in lowerConstantPtrAuth [AArch64] Add error handling for unsupported bases in lowerConstantPtrAuth Apr 7, 2026
Copy link
Copy Markdown
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@oskarwirga oskarwirga merged commit bc07dbe into llvm:main Apr 8, 2026
10 checks passed
@oskarwirga oskarwirga deleted the arm64e_asmprinter branch April 8, 2026 18:31
YonahGoldberg pushed a commit to YonahGoldberg/llvm-project that referenced this pull request Apr 21, 2026
…rAuth (llvm#189474)

This is part of work being done in llvm#188378 and llvm#188638, split out from
llvm#188650.

`lowerConstantPtrAuth` silently miscompiled `ConstantPtrAuth` constants
with non-`GlobalValue` pointer bases — emitting `0@AUTH(da,0)` instead
of erroring.

Changes:
- Handle `ConstantPointerNull` bases explicitly
- Error via `reportFatalUsageError` on any remaining unresolved base
(e.g. nested ptrauth) instead of silently miscompiling

This PR was mostly developed with LLM assistance
vikramRH pushed a commit to ROCm/llvm-project that referenced this pull request Apr 22, 2026
…rAuth (llvm#189474)

This is part of work being done in llvm#188378 and llvm#188638, split out from
llvm#188650.

`lowerConstantPtrAuth` silently miscompiled `ConstantPtrAuth` constants
with non-`GlobalValue` pointer bases — emitting `0@AUTH(da,0)` instead
of erroring.

Changes:
- Handle `ConstantPointerNull` bases explicitly
- Error via `reportFatalUsageError` on any remaining unresolved base
(e.g. nested ptrauth) instead of silently miscompiling

This PR was mostly developed with LLM assistance
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants