Перехват SIGSEGV при срабатывании поврежденным стеком

Я работал над некоторым ошибочным кодом и хотел установить обработчик SIGSEGV, чтобы получить больше информации о сбое. Однако я заметил, что мой обработчик не вызывается.

Я искал причину, и, похоже, это связано с поврежденным значением указателя стека (оно точно не маскируется). Вот код проверки концепции, который я написал для проверки:

static void catch_function(int sig, siginfo_t *info, void *cntxt)
{
    puts("handler works");
}

void main(int argc, char **argv)
{    
    struct sigaction sa;

    sa.sa_sigaction = (void *)catch_function;
    sigemptyset (&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO | SA_NODEFER ;

    sigaction(SIGSEGV, &sa, NULL);

    puts("testing handler");
    raise(SIGSEGV);
    puts("back");

    __asm__ (   "xor %rax, %rax\n\t"
                "mov %rax, %rsp\n\t"
                "push 0" 
            );

    // never reached...
}

Идея состоит в том, чтобы установить RSP в 0 (недопустимое смещение), а затем использовать его для чего-то. Однако этот второй SIGSEGV не будет перехвачен обработчиком, а завершит процесс.

По-видимому, для вызова обработчика сигнала для начала нужен указатель вменяемого стека - но почему? Разве это не противоречит идее обработки сигналов? Есть ли шанс обойти это?

Я использую Linux версии 3.19.0-25-generic.

1 ответ

Решение

Хорошо, вот решение вышеупомянутой проблемы после комментария EOF (используя sigaltstack() обеспечить стек сигналов в куче):

#include <stdio.h>

#define __USE_GNU
#include <signal.h>
#include <stdlib.h>
#include <ucontext.h>

static long long int sbase;

static void catch_function(int sig, siginfo_t *info, void *cntxt)
{
    puts("handler works");

    /* reset RSP if invalid */
    ucontext_t *uc_context = (ucontext_t *)cntxt;
    if(!uc_context->uc_mcontext.gregs[REG_RSP])
    {
        puts("resetting RSP");
        uc_context->uc_mcontext.gregs[REG_RSP] = sbase;
    }
}

void main(int argc, char **argv)
{    
    /* RSP during main */
    sbase = (long long int)&argv;

    stack_t ss;
    struct sigaction sa;

    ss.ss_sp = malloc(SIGSTKSZ);
    ss.ss_size = SIGSTKSZ;
    ss.ss_flags = 0;
    sigaltstack(&ss, NULL);  

    sa.sa_sigaction = (void *)catch_function;
    sigemptyset (&sa.sa_mask);
    sa.sa_flags = SA_SIGINFO | SA_NODEFER | SA_ONSTACK;

    sigaction(SIGSEGV, &sa, NULL);

    puts("testing handler");
    raise(SIGSEGV);
    puts("back");

    __asm__ (
            "xor %rax, %rax\n\t"
            "mov %rax, %rsp\n\t"
            "push %rax\n\t"
            "pop %rax"  );

    puts("exiting.");
}

Стек альтернативных сигналов размещается в куче и регистрируется с использованием sigaltstack(&ss,NULL), Так же SA_ONSTACK флаг установлен в sigaction структура, чтобы включить альтернативное использование стека для этого конкретного действия.

Это в основном решает мою проблему, потому что теперь мы видим бесконечный поток SIGSEGVбыть пойманным В конце концов, выше catch_function() не делает много, чтобы исправить неверный указатель стека. В качестве решения я теперь храню действительный указатель стека для main() в sbase и использовать это для восстановления его в обработчике, если он недействителен (посредством манипуляции с сохраненным контекстом потока).

Чтобы все это работало, я также исправил свою встроенную сборку, чтобы она не просто выдвигала значение, но и извлекала его впоследствии, чтобы высота стека оставалась неизменной. Ради возможности воспроизведения я также включил в этот раз включения.

Другие вопросы по тегам