Issue 134247
Summary lldb cannot call a function via a pointer that includes non-address bits (that aren't part of Top Byte Ignore)
Labels lldb
Assignees
Reporter DavidSpickett
    While looking into debug support for https://discourse.llvm.org/t/rfc-structure-protection-a-family-of-uaf-mitigation-techniques/85555/7 I found that I cannot call a function via a pointer if it has non-address bits that the processor still pays attention to.

The main use case of this is AArch64 pointer authentication. The existing test case `lldb/test/API/linux/aarch64/non_address_bit_code_break/main.c` can be used to show the problem:
```
#include <stdint.h>

void foo(void) {}
typedef void (*FooPtr)(void);

int main() {
  FooPtr fnptr = foo;
  // Set top byte.
  fnptr = (FooPtr)((uintptr_t)fnptr | (uintptr_t)0xff << 56);
  // Then apply a PAuth signature to it.
  __asm__ __volatile__("pacdza %0" : "=r"(fnptr) : "r"(fnptr));
  // fnptr is now:
  // <8 bit top byte tag><pointer signature><virtual address>

  foo(); // Set break point at this line.

  return 0;
}
```
Once we hit the breakpoint, `fnptr` will have a top byte tag and a pointer signature. The top byte tag isn't a problem because the hardware will ignore it (I'm on AArch64 with Top Byte Ignore enabled), but the signature will not be ignored.

This leads to a fault when you try to call the function:
```
(lldb) p fnptr()
         
error: _expression_ execution was interrupted: signal SIGSEGV: address not mapped to object (fault address=0x1faaaaaaaa0714).
The process has been returned to the state before _expression_ evaluation.
```
That address is the value of `fnptr`, minus the top byte, presumably because the hardware or kernel removed that for us.

LLDB does know that there are non address bits, and can remove them for other commands:
```
(lldb) p fnptr
(FooPtr) 0xff1faaaaaaaa0714 (actual=0x0000aaaaaaaa0714 test.o`foo at test.c:3:17)
(lldb) memory read fnptr
0xaaaaaaaa0714: 1f 20 03 d5 c0 03 5f d6 fd 7b be a9 fd 03 00 91  . ...._..{......
0xaaaaaaaa0724: 00 00 00 90 00 50 1c 91 e0 0f 00 f9 e0 0f 40 f9  .....P........@.
(lldb) process status -v
<...>
Addressable code address mask: 0xff7f000000000000
Addressable data address mask: 0xff7f000000000000
Number of bits used in addressing (code): 49
```
So at first I thought we just needed to add an `abi->FixCodeAddress` call somewhere, but nothing helped.

Then I realised, the problem is that we insert this `fnptr` into the JIT'd code as a symbol, it's not something we take the address of normally and we may not even care to check if it will be used for a function call.

`log enable lldb expr` shows:
```
lldb             == [UserExpression::Evaluate] Parsing _expression_ fnptr() ==
lldb ClangUserExpression::ScanContext()
lldb             Parsing the following code:
#line 1 "<lldb wrapper prefix>"
<...>
void 
$__lldb_expr(void *$__lldb_arg)          
{ 
    using $__lldb_local_vars::fnptr;
;                        
#line 1 "<user _expression_ 0>"
fnptr()
;
#line 1 "<lldb wrapper suffix>"
} 
<...>
lldb             Examining _ZN18$__lldb_local_varsL5fnptrE, DeclForGlobalValue returns 0x0000AB432C2BB4F8
lldb MaybeHandleVariable (@"_ZN18$__lldb_local_varsL5fnptrE" = external constant ptr, align 8)
lldb             Type of "fnptr" is [clang "FooPtr &", llvm "ptr"] [size 8, align 8]
lldb             Adding value for (NamedDecl*)0x0000AB432C2BB4F8 [fnptr - fnptr] to the structure
lldb Placed at 0x0
lldb             Element arrangement:
lldb Arg: "ptr %"$__lldb_arg""
lldb               "fnptr" ("fnptr") placed at 0
lldb                 Replacing [@"_ZN18$__lldb_local_varsL5fnptrE" = external constant ptr, align 8]
```
So we are placing the raw value of `fnptr` into the JIT context and trying to call that, which causes the fault.

```
(lldb) setting set target.process.unwind-on-error-in-expressions false
(lldb) p fnptr()
 
error: _expression_ execution was interrupted: signal SIGSEGV: address not mapped to object (fault address=0x50aaaaaaaa0714).
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before _expression_ evaluation.
Process 226746 stopped
* thread #1, name = 'test.o', stop reason = signal SIGSEGV: address not mapped to object (fault address=0x50aaaaaaaa0714)
    frame #0: 0x0000aaaaaaaa0714 test.o`foo at test.c:3:17
   1   	#include <stdint.h>
   2   	
-> 3   	void foo(void) {}
<...>
(lldb) bt
* thread #1, name = 'test.o', stop reason = signal SIGSEGV: address not mapped to object (fault address=0x50aaaaaaaa0714)
  * frame #0: 0x0000aaaaaaaa0714 test.o`foo at test.c:3:17
    frame #1: 0x0000fffff7ff508c $__lldb_expr`$__lldb_expr($__lldb_arg=0x0000fffff7ff3000) at <user _expression_ 0>:1
    frame #2: 0x0000aaaaaaaa0600 test.o`abort + 16 
(lldb) up
frame #1: 0x0000fffff7ff508c $__lldb_expr`$__lldb_expr($__lldb_arg=0x0000fffff7ff3000) at <user _expression_ 0>:1
(lldb) dis
$__lldb_expr`$__lldb_expr:
<...>
    0xfffff7ff5088 <+88>:  blr    x8
->  0xfffff7ff508c <+92>:  ldr    x19, [sp, #0x10]
```

The same issue applies if you're trying to emulate the effect of Pointer Authentication and use the `target.process.virtual-addressable-bits` setting to ignore bits.

In a program like the one above we have no way to know if a pointer is signed, but we might with a program compiled for an ABI that always does that.

We could check whether the type is a function pointer when we set up the JIT context, and fix it then. Though you could break that, by casting something into a function pointer, but at that point you're asking for trouble anyway.
_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to