3

I'm trying to modify a return address (some levels down) on the call stack. I need to do this when I am inside a signal handler. Therefore I'm doing the following:

#include <csignal>
#include <cstdint>
#include <iostream>

// To print stacktrace
#include <execinfo.h>
#include <stdlib.h>

void printAround(uint64_t* p, int min=0, int max=3) {
    for(int i = min; i <= max; ++i) {
        std::cout << std::dec << ((i >= 0) ? " " : "") << i << ": "
                  << std::hex
                  << reinterpret_cast<uint64_t>(*(p + i))
                  << std::dec << std::endl;
    }
    std::cout << "================================================" << std::endl;
}

void sigHandler(int signum) {
    register uint64_t* EBP asm ("rbp");
    printAround(EBP);

    uint64_t *oldEBP = reinterpret_cast<uint64_t*>(*EBP);
    printAround(oldEBP);

    oldEBP = reinterpret_cast<uint64_t*>(*oldEBP);
    printAround(oldEBP);

    /* PRINT STACK TRACE!! POSSIBLY UNSAFE! */
    void *array[10];
    size_t size;
    char **strings;
    size_t i;
    size = backtrace(array, 10);
    strings = backtrace_symbols(array, size);
    std::cout << "\nObtained " << size << " stack frames.\n";
    for (i = 0; i < size; i++) {
        std::cout << strings[i] << "\n";
    }
    free(strings);
    /* END PRINT STACK TRACE !! */
}

int foo(void) {
    std::raise(SIGTRAP);
    return 5;
}

int baz(void) {
    return foo() + 10;
}

int bar(void) {
    return baz() + 15;
}

int main(int argc, char **argv) {
    // SIGTRAP is 0xCC
    std::signal(SIGTRAP, &sigHandler);
    return bar();
}

The corresponding output is:

 0: 7ffda9664a10
 1: 7faf5777c4b0
 2: 1
 3: 0
================================================
 0: 7ffda9664a20
 1: 557f2a7bdf21
 2: 7ffda9664a30
 3: 557f2a7bdf2f
================================================
 0: 7ffda9664a30
 1: 557f2a7bdf2f
 2: 7ffda9664a50
 3: 557f2a7bdf59
================================================

Obtained 9 stack frames.
./main(+0xe41) [0x557f2a7bde41]
/lib/x86_64-linux-gnu/libc.so.6(+0x354b0) [0x7faf5777c4b0]
/lib/x86_64-linux-gnu/libc.so.6(gsignal+0x38) [0x7faf5777c428]
./main(+0xf11) [0x557f2a7bdf11]  => foo
./main(+0xf21) [0x557f2a7bdf21]  => baz
./main(+0xf2f) [0x557f2a7bdf2f]  => bar
./main(+0xf59) [0x557f2a7bdf59]  => main
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7faf57767830]
./main(+0xba9) [0x557f2a7bdba9]

At offset 0 is the base pointer of the previous stack, and at offset 1 us the return address.

As one can see in the output, the first return address is the first function in libc, but the next one is already baz and not foo or the other libc function as I would have expected.

When I remove the signal handler and place the logic to print the stack in foo I see all of my functions: foo, baz, bar, main...

What am I missing here? I do need to modify the return address to the function in witch the signal was triggered, i.e., foo, but this one is skipped in my stack unwind logic :(

P.S. I do know that it is not safe to use backtrace [2] in a signal handler, since it leads to undefined behaviour! Seems I'm being lucky here and the problem persists when I delete all the backtrace logic!

Also if anyone has any other ideas how to solve this problem I'm happy if you would share. I tried to use __builtin_frame_address() with an argument >0 but this crashes inside the signal handler [1]. There seems to be something different, and I can't find any information about the what.

[1] https://gcc.gnu.org/onlinedocs/gcc/Return-Address.html

[2] https://www.gnu.org/software/libc/manual/html_node/Backtraces.html

datosh
  • 498
  • 7
  • 20
  • 1
    1) `std::cout << std::dec` This is not C 2) you cannot use printf (and friends) from within a signal handler. – wildplasser Jan 25 '17 at 18:08
  • 1) sorry removed the wrong tag, had both and deleted c++. 2) so what do I do? use the signal handler with a void* out arg and write some data struct which I later print to stdout? Would this help with my problem? I mean does printing and calling and calling functions might lead to this "unexpected behaviour"? – datosh Jan 25 '17 at 19:14
  • 2
    Printf() is not async-safe. (basically because it can call malloc() inside, which is not async-safe). Best way is probably to use a global (static) buffer. The bad news: sprintf() is not async-safe, either :-[ – wildplasser Jan 25 '17 at 19:29
  • I think you may want to use `uintptr_t` instead of `uint64_t` to be more portable... – yugr Jan 25 '17 at 21:42

2 Answers2

4

The solution to modify the return address from within a signal handler, requires a different method to register the signal handler in the first place.

Frist the code:

#include <csignal>
#include <cstdint>
#include <iostream>

void signal_handler(int signal, siginfo_t *si, void *context)
{
    const int return_delta = 2;
    ((ucontext_t*)context)->uc_mcontext.gregs[REG_RIP] += return_delta;
}


int foo(void)
{
    asm(".byte 0xcc\n");

    // "for(;;) ;" in a way that prevents the compiler from recognizing the
    // remainder of the function as dead code and optimizing it away...
    asm(".byte 0xEB\n");
    asm(".byte 0xFE\n");

    return 5;
}


int baz(void) {
    return foo() + 10;
}


int bar(void) {
    return baz() + 15;
}


int main(int argc, char **argv)
{
    struct sigaction sa = {0};
    sigemptyset(&sa.sa_mask);
    sa.sa_sigaction = signal_handler;
    sa.sa_flags = SA_SIGINFO;

    // Install signal handler
    sigaction(SIGTRAP, &sa, NULL);

    // So that we see some output
    size_t i{1000};
    while(i--) {
        std::cout << bar() << std::endl;
    }
    return 0;
}

sigaction in conjunction with sigaction can be used to pass more information to the signal handler. If you do so, siginfo_t is passed to the signal handler which contains all sort of information about the signal itself.

Additionally a void *context is passed to the registered signal handler which contains information about the state of the registers and stack. You can use this to manipulate the return address into the signal raising function as you like.

Note that this is highly platform specific you should use your ucontext.h to see how the struct look for your specific platform.

datosh
  • 498
  • 7
  • 20
2

Hm, where do I start...

Firstly, I'm afraid x64 does not have frame pointers by default so there is no easy way to reconstruct stack by following chain of rbps. Furthermore, even if you recompiled all participating code (including Glibc!) with -fno-omit-frame-pointer signal handler's frame is probably set up in a very special manner by kernel so there's no guarantee that you'll be able to unwind through it via frame pointer. BTW that's probably the reason __builtin_frame_address runtime failures.

Next you mention that you want to unwind by changing return address behind compiler's back. Nothing would be a better recipe for runtime crash. Compiler saves tons of critical information in function frames. This info is preserved by strict contracts between calling and called functions (so called "calling convention"). By altering return address, you'd discard all this info and most likely return to target code with random garbage in registers.

The only sane way to implement stack unwinding is to use (or at least re-implement) existing unwinders (in libgcc, libunwind or, preferably, in libbacktrace).

yugr
  • 19,769
  • 3
  • 51
  • 96