Перехват 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
и использовать это для восстановления его в обработчике, если он недействителен (посредством манипуляции с сохраненным контекстом потока).
Чтобы все это работало, я также исправил свою встроенную сборку, чтобы она не просто выдвигала значение, но и извлекала его впоследствии, чтобы высота стека оставалась неизменной. Ради возможности воспроизведения я также включил в этот раз включения.