2016-09-02 9 views
1

Ich habe an einigen Buggy-Code gearbeitet und wollte einen SIGSEGV-Handler installieren, um weitere Informationen über den Absturz zu erhalten. Ich habe jedoch festgestellt, dass mein Handler nicht aufgerufen wird.Catching SIGSEGV, wenn durch korrupten Stack ausgelöst

Ich habe nach einem Grund gesucht und es scheint, dass es mit einem korrupten Stack-Pointer-Wert zu tun hat (es wird nicht sicher maskiert). Hier ist ein Proof-of-Concept-Code schrieb ich bis zu überprüfen:

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... 
} 

Die Idee RSP ist auf 0 gesetzt (ungültig Offset) und dann für etwas verwenden. Dieser zweite SIGSEGV wird jedoch nicht vom Handler abgefangen, sondern beendet stattdessen den Prozess.

Offenbar benötigt der Aufruf des Signalhandlers einen vernünftigen Stack-Pointer, um damit zu beginnen - aber warum? Ist das nicht gegen die Idee, mit Signalen umzugehen? Irgendeine Chance, das zu umgehen?

Ich verwende Linux Version 3.19.0-25-Generic.

+1

'sigaltstack()'. – EOF

+0

@EOF - Danke; Das sieht vielversprechend aus. Wird die Lösung erfolgreich veröffentlichen. – HairyNopper

+0

können Sie versuchen, Ihr Programm in Valgrind auszuführen? – bruceg

Antwort

1

Okay, hier ist eine Lösung für das obige Problem folgenden EOF Kommentar (sigaltstack() ein Signal Stapel auf dem Heap zu schaffen, verwenden):

#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."); 
} 

Der alternative Signalstapel auf dem Heap zugewiesen und registriert mit sigaltstack(&ss,NULL) . Außerdem wird das Flag SA_ONSTACK in der Struktur gesetzt, um den alternativen Stack für diese spezifische Aktion zu aktivieren.

Das löst im Grunde mein Problem, denn jetzt sehen wir einen endlosen Strom von SIGSEGV s gefangen. Immerhin macht das obige catch_function() nicht viel, um den ungültigen Stapelzeiger zu reparieren. Als Lösung speichere ich jetzt den gültigen Stapelzeiger für die main() in sbase und benutze das, um es im Handler wiederherzustellen, wenn es ungültig ist (durch Manipulation des gespeicherten Thread-Kontexts).

Um all das zu arbeiten, habe ich auch meine Inline-Assembly so eingestellt, dass sie nicht nur einen Wert verschiebt, sondern auch wieder zurückstellt, so dass die Stapelhöhe unverändert bleibt. Aus Gründen der Reproduzierbarkeit habe ich auch die Includes dieses Mal aufgenommen.

Verwandte Themen