| Issue |
184660
|
| Summary |
PAuth-protected libunwind is unable to unwind through sigreturn stack frame
|
| Labels |
new issue
|
| Assignees |
|
| Reporter |
atrosinenko
|
When stack unwinding is performed from inside a signal handler on aarch64-linux-pauthtest target, unwinding stops at the signal handler and does not visit regular stack frames. Without `_LIBUNWIND_TARGET_AARCH64_AUTHENTICATED_UNWINDING`, on the other hand, libunwind is able to cross the signal stack / normal stack boundary as long as runtime libraries are built with unwind info.
Using libunwind, it is possible to start stack unwinding from inside a signal handler and cross the signal stack / normal stack boundary. While C++ exception handling doesn't seem to interfere with POSIX signals in a well-defined way, stack unwinding is also useful for crash reporting. As far as I can see, [crash reporting in LLVM](https://github.com/llvm/llvm-project/blob/a7914aecb2db16274ae07d36ae07178707eac89b/llvm/lib/Support/Unix/Signals.inc#L786) is itself an example of using libunwind to report the call stack and is highly likely to deal with signals.
Depending on the target, custom code may be required to step from signal-handling call stack to the regular call stack. On such targets, libunwind has first to recognize it is about to switch from the signal call stack to the regular one ([`UnwindCursor::stepThroughSigReturn`](https://github.com/llvm/llvm-project/blob/a7914aecb2db16274ae07d36ae07178707eac89b/libunwind/src/UnwindCursor.hpp#L2888). If the current frame is a sigreturn one, [`UnwindCursor::setInfoForSigReturn`](https://github.com/llvm/llvm-project/blob/a7914aecb2db16274ae07d36ae07178707eac89b/libunwind/src/UnwindCursor.hpp#L2958) actually performs the step.
## Steps to reproduce
Build a pauth-enabled toolchain using https://github.com/access-softek/pauth-toolchain-build-scripts with LLVM_BRANCH / LLVM_SHA in `config` pointing to a recent version of LLVM.
Compile this example:
```cpp
#include <dlfcn.h>
#include <ptrauth.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <unwind.h>
#define MAX_FRAMES 128
volatile uint64_t StackTrace[MAX_FRAMES];
volatile int NumFrames = 0;
// Signal-safe alternatives to fprintf(stderr, ...)
void WriteStr(const char *Str);
void WriteHexU64(uint64_t Num);
void * volatile Page;
#define NOINLINE __attribute__((noinline))
_Unwind_Reason_Code HandleFrame(struct _Unwind_Context *Ctx, void *Arg) {
(void)Arg;
uint64_t IP = _Unwind_GetIP(Ctx);
WriteStr("IP: ");
WriteHexU64(IP);
WriteStr("\n");
if (NumFrames < MAX_FRAMES)
StackTrace[NumFrames++] = IP;
return IP ? _URC_NO_REASON : _URC_END_OF_STACK;
}
void Handler(int) {
WriteStr("In signal handler\n");
_Unwind_Backtrace(HandleFrame, NULL);
// Prevent infinite loop due to store instruction being retried after
// returning from the signal handler.
mprotect(Page, 4096, PROT_READ|PROT_WRITE);
}
NOINLINE void Signaller() {
// For simplicity, make sure we are not in a leaf function
// (force signing the LR register).
asm volatile("" : : : "lr");
// Trigger a SIGSEGV by writing to a memory page initially mapped read-only.
// The signal handler remaps this memory page read-write, so that this does
// not fault after restarting when signal handler finishes.
*(uint64_t *)Page = 0;
}
NOINLINE void Caller(int Counter) {
if (Counter)
Caller(Counter - 1);
else
Signaller();
}
NOINLINE void DecodeStackTrace() {
WriteStr("\n\nDecoded stack trace:\n");
for (int I = 0; I <NumFrames; ++I) {
void *IP = ptrauth_strip((void *)StackTrace[I], 0);
Dl_info Info;
if (!dladdr(IP, &Info) || !Info.dli_sname) {
WriteStr("<unknown>\n");
continue;
}
WriteStr(Info.dli_sname);
WriteStr("\n");
}
}
int main() {
void *HandlerWithoutPAC = ptrauth_strip((void*)Handler, 0);
signal(SIGSEGV, (void (*)(int))HandlerWithoutPAC);
Page = mmap(NULL, 4096, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
Caller(3);
DecodeStackTrace();
return 0;
}
void WriteStr(const char *Str) {
const int StdErrFD = 2;
write(StdErrFD, Str, strlen(Str));
}
void WriteHexU64(uint64_t Num) {
const int NumDigits = 16;
char Buf[NumDigits + 1];
Buf[NumDigits] = '\0';
for (int I = NumDigits - 1; I >= 0; --I) {
unsigned Digit = Num & 0xFu;
Num >>= 4;
Buf[I] = (Digit < 10) ? ('0' + Digit) : ('a' + Digit - 10);
}
WriteStr("0x");
WriteStr(Buf);
}
```
with
```
# assuming the toolchain is at /opt/llvm-pauth
triple=aarch64-linux-musl; lib=/opt/llvm-pauth/$triple/usr/lib;
/opt/llvm-pauth/bin/clang++ \
-target $triple -march=v8.3-a \
-Wl,--dynamic-linker=$lib/libc.so \
-Wl,--rpath=$lib \
/tmp/unwind-stacktrace.cpp -o /tmp/unwind-stacktrace
```
The output of `/tmp/unwind-stacktrace` is along these lines
```
In signal handler
IP: 0x0000005ff57f0ea4
IP: 0x0000007a62040004
IP: 0x0000005ff57f0ed0
IP: 0x0000005ff57f0f0c
IP: 0x0000005ff57f0f04
IP: 0x0000005ff57f0f04
IP: 0x0000005ff57f0f04
IP: 0x0000005ff57f1038
IP: 0x0000007a6200b6dc
Decoded stack trace:
_Z7Handleri
__setjmp
_Z9Signallerv
_Z6Calleri
_Z6Calleri
_Z6Calleri
_Z6Calleri
main
<unknown>
```
In the compiler invocation, change `triple=aarch64-linux-musl` to `triple=aarch64-linux-pauthtest`. Then output is
```
In signal handler
IP: 0x0000006077d70fd0
Decoded stack trace:
_Z7Handleri
```
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs