2011-01-28 12 views
11

Właśnie się dowiedziałem, że ktoś dzwoni - od obsługi sygnału - zdecydowanie nie jest to funkcja bezpieczna dla sygnałów asynchronicznych, którą napisałem. I, oczywiście, obarczam się winą (pomimo ostrzeżeń w mojej dokumentacji). (Ten sam koder wywołuje wszystkie funkcje niesynchronizowane z sygnałem z jego nośnika sygnału.) Westchnienie.)Jak ustalić, czy kod działa w kontekście obsługi sygnału?

Więc teraz jestem ciekawy: jak uniknąć tej sytuacji? Chciałbym być w stanie łatwo określić, czy mój kod jest uruchomiony w kontekście procedury obsługi sygnału (język C, ale nie rozwiązanie stosuje się do każdego języka):

int myfunc(void) { 
    if(in_signal_handler_context()) { return(-1) } 
    // rest of function goes here 
    return(0); 
} 

to pod Linuksem. Mam nadzieję, że to nie jest łatwa odpowiedź, bo inaczej poczułbym się jak idiota.

+0

Zrozumiano. Jednak, kiedy programista po raz pierwszy napotyka na koncepcję sygnałów, takie rzeczy zawsze się zdarzają. Zgadnij, co się stanie, gdy destruktor obiektu C++ zostanie na siłę wywołany z programu obsługi sygnału? – smcdow

Odpowiedz

7

Wygląda na to, że nowszy system Linux/x86 (prawdopodobnie od czasu pojawienia się jądra 2.6.x) wywołuje procedury obsługi sygnałów od vdso. Możesz użyć tego faktu, by zadać następujące okropne włamanie do niczego nie podejrzewającego świata:

#include <stdio.h> 
#include <stdlib.h> 
#include <stdint.h> 
#include <string.h> 
#include <signal.h> 

#include <unistd.h> 

uintmax_t vdso_start = 0; 
uintmax_t vdso_end = 0;    /* actually, next byte */ 

int check_stack_for_vdso(uint32_t *esp, size_t len) 
{ 
    size_t i; 

    for (i = 0; i < len; i++, esp++) 
      if (*esp >= vdso_start && *esp < vdso_end) 
        return 1; 

    return 0; 
} 

void handler(int signo) 
{ 
    uint32_t *esp; 

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); 
    /* XXX only for demonstration, don't call printf from a signal handler */ 
    printf("handler: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); 
} 

void parse_maps() 
{ 
    FILE *maps; 
    char buf[256]; 
    char path[7]; 
    uintmax_t start, end, offset, inode; 
    char r, w, x, p; 
    unsigned major, minor; 

    maps = fopen("/proc/self/maps", "rt"); 
    if (maps == NULL) 
      return; 

    while (!feof(maps) && !ferror(maps)) { 
      if (fgets(buf, 256, maps) != NULL) { 
        if (sscanf(buf, "%jx-%jx %c%c%c%c %jx %u:%u %ju %6s", 
            &start, &end, &r, &w, &x, &p, &offset, 
            &major, &minor, &inode, path) == 11) { 
          if (!strcmp(path, "[vdso]")) { 
            vdso_start = start; 
            vdso_end = end; 
            break; 
          } 
        } 
      } 
    } 

    fclose(maps); 

    printf("[vdso] at %jx-%jx\n", vdso_start, vdso_end); 
} 

int main() 
{ 
    struct sigaction sa; 
    uint32_t *esp; 

    parse_maps(); 
    memset(&sa, 0, sizeof(struct sigaction)); 
    sa.sa_handler = handler; 
    sa.sa_flags = SA_RESTART; 

    if (sigaction(SIGUSR1, &sa, NULL) < 0) { 
      perror("sigaction"); 
      exit(1); 
    } 

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); 
    printf("before kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); 

    kill(getpid(), SIGUSR1); 

    __asm__ __volatile__ ("mov %%esp, %0" : "=r"(esp)); 
    printf("after kill: check_stack_for_vdso() = %d\n", check_stack_for_vdso(esp, 20)); 

    return 0; 
} 

SCNR.

+0

Nie wiedziałem, że procedury obsługi sygnału zostały wywołane z vdso. Czy możesz wskazać odniesienie? W każdym razie lubię ten hack. Dużo. Łatwo byłoby przekształcić to w nieprzezroczystą bibliotekę. Sztuczka polegałaby na upewnieniu się, że funkcja parse_maps() została wywołana przed jakimkolwiek modułem obsługi sygnałów. – smcdow

+0

Najlepsze referencje, które mogę znaleźć, to http://lxr.free-electrons.com/source/arch/x86/kernel/signal.c?v=2.6.37#L310 – ninjalj

+0

Ale komentarz w linii 320 wygląda interesująco: http : //lxr.free-electrons.com/source/arch/x86/kernel/signal.c? v = 2.6.37 # L320 – ninjalj

0

Istnieją dwa właściwe sposoby radzenia sobie z tym:

  • Czy twoi współpracownicy przestać robić coś złego. Powodzenia z tym szefem ...

  • Spraw, aby Twoja funkcja była ponownie dostępna i asynchroniczna. Jeśli to konieczne, podaj funkcję z innym podpisem (na przykład używając powszechnie używanej konwencji nazewnictwa) z dodatkowymi argumentami niezbędnymi do zachowania stanu.

chodzi o bez właściwego sposób to zrobić na Linuksie z LIBC można użyć backtrace() i przyjaciół, aby przejść do listy połączeń swojej funkcji. To nie łatwo dostać prawo, bezpieczne lub przenośny, ale może to zrobić na chwilę:

/* 
* *** Warning *** 
* 
* Black, fragile and unportable magic ahead 
* 
* Do not use this, lest the daemons of hell be unleashed upon you 
*/ 
int in_signal_handler_context() { 
     int i, n; 
     void *bt[1000]; 
     char **bts = NULL; 

     n = backtrace(bt, 1000); 
     bts = backtrace_symbols(bt, n); 

     for (i = 0; i < n; ++i) 
       printf("%i - %s\n", i, bts[i]); 

     /* Have a look at the caller chain */ 
     for (i = 0; i < n; ++i) { 
       /* Far more checks are needed here to avoid misfires */ 
       if (strstr(bts[i], "(__libc_start_main+") != NULL) 
         return 0; 
       if (strstr(bts[i], "libc.so.6(+") != NULL) 
         return 1; 
     } 

     return 0; 
} 


void unsafe() { 
     if (in_signal_handler_context()) 
       printf("John, you know you are an idiot, right?\n"); 
} 

W moim zdaniem, może być tylko lepiej rzucić zamiast być zmuszony do pisania kodu jak to.

+0

Właśnie próbowałem 'backtrace()', a to po prostu nie działa: '__libc_start_main' jest w śladzie zarówno w kontekście obsługi sygnału, jak i poza nim. –

+0

Jak już wspomniałem, nie jest to łatwe. Musisz znaleźć różnicę w śledzeniu między tymi dwoma przypadkami i użyć tego. Na przykład dla mojego testu założyłem, że żadna funkcja libc nie znajdzie się w śladzie wstecznym przed osiągnięciem 'main()', chyba że jest to kod obsługi sygnału. Jak wygląda twój ślad kontrolny w każdym przypadku? – thkala

+0

Dwie uwagi: (1) Obawiałem się, że może to być coś takiego. (2) Nie być smarkaczem, ale printf (3) nie jest funkcją bezpieczną dla sygnałów asynchronicznych. Musisz użyć write (2). - Listę funkcji bezpiecznych dla sygnałów asynchronicznych (przynajmniej listę, do której zazwyczaj się odwołuję) można znaleźć tutaj: http://pubs.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html – smcdow

0

Możesz coś wymyślić, używając sigaltstack. Skonfiguruj alternatywny stos sygnału, uzyskaj wskaźnik stosu w sposób asynchroniczny, jeśli w alternatywnym stosie będzie dalej, w przeciwnym razie przerywaj().

+0

Myślałem o czymś takim, ale nie sądzę, żebym mógł zagwarantować, że będę miał alternatywny stos, zanim moja funkcja zostanie wywołana z programu obsługi sygnału. Powinienem też powtórzyć, że moja funkcja NIGDY nie powinna być wywołana z programu obsługi sygnału, a dokumentacja tak mówi. – smcdow

+0

Jest łatwiejszy sposób. 'sigaltstack' jest wymagany, aby zwrócić błąd, jeśli już używasz alternatywnego stosu i próbujesz wprowadzić w nim zmiany, więc możesz po prostu spróbować wywołać go i sprawdzić, czy połączenie nie powiedzie się. –

0

Zgaduję, że musisz wykonać następujące czynności. Jest to kompleksowe rozwiązanie, łączące najlepsze praktyki nie tylko z kodowaniem, ale także z inżynierią oprogramowania!

  1. Przekonaj szefa, że ​​konwencja nazewnictwa dotycząca programów obsługi sygnałów jest dobrą rzeczą. Zaproponuj, na przykład, Hungarian notation, i powiedz, że był on używany w Microsoft z wielkim sukcesem. Wszystkie procedury obsługi sygnałów rozpoczynają się od sighnd, np. sighndInterrupt.
  2. Twoja funkcja, która wykrywa kontekst obsługi sygnału byłoby wykonać następujące czynności:
    1. Pobierz backtrace().
    2. Sprawdź, czy którakolwiek z funkcji zaczyna się od sighnd.... Jeśli tak, to gratuluję, jesteś w obsłudze sygnału!
    3. W przeciwnym razie nie jesteś.
  3. Spróbuj unikać pracy z Jimmy w tej samej firmie. "Może być tylko jeden", wiesz.
+0

Będziemy musieli przejąć dozorcę przez bazę kodu i zgłosić wszystkie funkcje wywoływane z procedur obsługi sygnałów. Już się skręcam. – smcdow

+0

@smcdow, przy okazji, można użyć do tego narzędzia analizy statycznej. Ale znowu trzeba by było opisywać każdy program obsługi sygnału, co sprawia, że ​​proponowane rozwiązanie nie jest bardziej skomplikowane. :-) –

+0

jakiś czas temu istniało narzędzie Valgrind, które szukało problemów z obsługą sygnału, nazywane było krokusami. – ninjalj

0

kodu zoptymalizowanego na -O2 lub lepsza (Istr) odkryli potrzebę dodania -fno-pomijanie-frame-pointer

inny gcc zoptymalizuje informacje kontekstowe stos

3

Jeśli możemy założyć swoje aplikacja nie blokuje ręcznie sygnałów za pomocą sigprocmask() lub pthread_sigmask(), to jest całkiem proste: uzyskaj bieżący identyfikator wątku (tid). Otwórz /proc/tid/status i uzyskaj wartości dla SigBlk i SigCgt. AND te dwie wartości. Jeśli wynik tego AND jest różny od zera, wątek ten jest aktualnie uruchomiony z wewnątrz procedury obsługi sygnału. Testowałem to sam i to działa.

+0

Potrzebowałbyś ID procesu (PID), a nie identyfikatora wątku. Wykonanie tego będzie wymagało wywoływania funkcji niesynchronizowanych z sygnałem, z wyłączeniem pisania własnego. – mgarey