Skip to content

Signal handler race condition

Bir signal handler, non-reentrant code'u kesintiye uğratır (veya onunla state paylaşır), dolayısıyla async delivery global/heap yapılarını corrupt eder — CWE-364.

Mechanism

Invariant

Bir signal handler, başka bir fonksiyonun ortası dahil herhangi bir instruction boundary'sinde asenkron olarak ateşlenebilir. Eğer handler, kesintiye uğrayan code'un da değiştirmekte olduğu state'e dokunursa — global/static değişkenler ya da internal allocator metadata'sı — sonuç, onu serialize edecek bir lock'u olmayan bir data race'tir. Klasik durum: bir handler'ın async-signal-safe olmayan bir fonksiyon çağırması.

Async-signal-safe küme (bkz. man 7 signal-safety) küçüktür ve malloc(), free(), printf()/syslog() ile libc'nin çoğunu dışlar, çünkü bunlar internal state'i (örneğin heap free-list'i, stdio buffer'ları) hiçbir signal-safe lock altında olmadan tutar. Bir signal geldiğinde main malloc() içindeyse ve handler malloc()/free() çağırırsa (genellikle syslog() yoluyla dolaylı olarak), ikisi de aynı yarı-güncellenmiş heap metadata'sına karşı çalışır — double-free, heap corruption ya da zaten free edilmiş bir global'in re-entrant free'i üretir. CWE-364'e göre bu, data corruption'a, code execution'a ya da — kesintiye uğrayan code ayrıcalık taşıyorsa — privilege escalation'a tırmanabilir. İki signal için kayıtlı aynı handler (örneğin SIGHUP ve SIGTERM), handler'ı fiilen kendi içine re-enter ettirir.

Walkthrough

Bir global'i free eden ve log'layan, SIGHUP ve SIGTERM tarafından paylaşılan bir handler — CWE-364'ün gösterici güvenlik açığı:

#include <signal.h>
#include <stdlib.h>
#include <syslog.h>
#include <string.h>

char *global2;
void *global1;

void sh(int sig) {
    syslog(LOG_NOTICE, "%s\n", global2);   /* syslog() -> malloc(): NOT async-signal-safe */
    free(global1);                          /* re-entrant free of a global pointer        */
    global1 = NULL;
}

int main(void) {
    global1 = malloc(8);
    global2 = strdup("msg");
    signal(SIGHUP,  sh);
    signal(SIGTERM, sh);                    /* same handler on two signals -> reentrancy   */
    while (1) pause();
}

İlk handler hâlâ syslog/malloc içindeyken ikinci bir signal göndererek race'i tetikle:

$ gcc -g vuln.c -o vuln && ./vuln &
[1] 5123
$ kill -HUP 5123 ; kill -TERM 5123      # SIGTERM interrupts sh() before global1 is NULLed

Beklenen çıktı — glibc, global1'in double free()'sini tespit eder:

*** Error in `./vuln': double free or corruption (fasttop): 0x000055e2a1c1b260 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f...]
======= Memory map: ========
Aborted (core dumped)
Güvenli pattern: handler sadece bir flag set eder

Tüm gerçek işi main loop'a erteleyerek async-signal-safety korunur:

#include <signal.h>
volatile sig_atomic_t got_signal = 0;     /* the only signal-safe shared type */

void handler(int sig) { got_signal = 1; }  /* set-flag only; no malloc/stdio   */

int main(void) {
    signal(SIGTERM, handler);
    for (;;) {
        if (got_signal) { /* do free()/logging here, in normal context */ }
        pause();
    }
}
write(2) async-signal-safe'tir ve handler içinde kullanılabilir; printf/syslog/malloc/free kullanılamaz.

Detection

  • Statik analiz: kayıtlı bir handler'dan ulaşılabilen async-signal-safe olmayan bir fonksiyona (malloc, free, printf, syslog, setjmp/longjmp) yapılan herhangi bir çağrıyı işaretle (CERT C SIG30-C/SIG31-C).
  • Runtime: glibc'nin double free or corruption abort'ları ya da signal stress altında ASan/Valgrind'in heap tutarsızlıklarını raporlaması güçlü göstergelerdir.

Mitigation

  • Sadece bir flag set et. Handler'lar tek bir volatile sig_atomic_t yazıp return etmeli; gerçek işi main loop'ta yap.
  • Handler'lar içinde yalnızca async-signal-safe fonksiyonlar kullan (man 7 signal-safety).
  • Shared state'e dokunan critical section'ların etrafında sigprocmask()/pthread_sigmask() ile signal'leri block et; signal'leri senkron ele almak için signalfd()/self-pipe'ı tercih et.
  • Shared state'i mutate ettiğinde bir handler'ı birden çok signal için kaydetmekten kaçın.

References