Signal-Handler-Race-Condition
Beschreibung
Signal-Handler-Race-Condition ist eine Schwachstelle, die auftritt, wenn Signal-Handler durch asynchrone Aktionen Race Conditions einführen. Signale können normale Programmausführung an praktisch jedem Punkt unterbrechen und Signal-Handler im Kontext des unterbrochenen Codes ausführen. Wenn Signal-Handler nicht-reentrant-Funktionen verwenden, globale Variablen modifizieren oder zustandssensitive Operationen durchführen, können sie Annahmen verletzen, die vom unterbrochenen Code oder anderen Signal-Handlern gemacht wurden. Das Kernproblem ist, dass Signale während kritischer Abschnitte ankommen können, in denen Datenstrukturen in inkonsistenten Zuständen sind, was zu Korruption, Abstürzen oder ausnutzbaren Bedingungen führt, wenn der Handler auf diese Strukturen zugreift.
Risiko
Signal-Handler-Race-Conditions können zu schwerwiegenden Sicherheitsschwachstellen führen, einschließlich beliebiger Codeausführung, Privilegieneskalation und Denial-of-Service. Wenn Handler Funktionen wie malloc/free aufrufen, während das Hauptprogramm ebenfalls in malloc/free ist, können Double-Free- oder Use-After-Free-Bedingungen auftreten. Handler, die globalen Zustand modifizieren, können Datenstrukturen korrumpieren. Angreifer können remote Signale auslösen (wie SIGURG für dringende TCP-Daten), um diese Bedingungen auszunutzen. Das unvorhersagbare Timing macht diese Schwachstellen schwer reproduzierbar, aber Angreifer können ihre Chancen durch wiederholte Versuche oder durch Kontrolle des Signaleintrittszeitpunkts erhöhen.
Lösung
Halten Sie Signal-Handler minimal - idealerweise setzen sie nur ein Flag, das das Hauptprogramm prüft. Rufen Sie nur async-signal-sichere Funktionen aus Handlern auf (dokumentiert in signal-safety(7) unter Linux). Blockieren Sie Signale während kritischer Abschnitte mit sigprocmask. Vermeiden Sie den Zugriff auf globale Variablen in Handlern, es sei denn, sie sind volatile sig_atomic_t. Verwenden Sie sigaction mit SA_RESTART, um unterbrochene Systemaufrufe zu behandeln. Erwägen Sie die Verwendung von signalfd() unter Linux, um Signale synchron zu behandeln. Rufen Sie niemals malloc, free, printf oder andere nicht-reentrant-Funktionen aus Signal-Handlern auf.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Integrität | Umfang: Integrität Datenkorruption und mögliche beliebige Codeausführung durch Modifikation globaler Variablen zu unerwarteten Zeiten. |
| Verfügbarkeit | Umfang: Verfügbarkeit Systemabstürze durch Double-Free oder korrumpierte Datenstrukturen, wenn Handler kritische Abschnitte unterbrechen. |
| Zugriffskontrolle | Umfang: Zugriffskontrolle Privilegieneskalation, wenn Handler Code unterbrechen, der mit erhöhten Privilegien läuft. |
Beispielcode
Anfälliger Code
// Anfällig: Nicht-reentrant-Funktion in Signal-Handler
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
char *global_buffer = NULL;
void vulnerable_handler(int sig) {
// Anfällig: free() ist nicht async-signal-sicher
// Wenn Signal während malloc/free ankommt, tritt Korruption auf
free(global_buffer);
global_buffer = malloc(100); // Ebenfalls unsicher
// Anfällig: printf ist nicht async-signal-sicher
printf("Signal empfangen\n");
}
int main() {
signal(SIGINT, vulnerable_handler);
while (1) {
// Wenn Signal hier ankommt, während malloc, kann Double-Free auftreten
global_buffer = malloc(1000);
// ... Buffer verwenden ...
free(global_buffer);
}
}
// Anfällig: Globale Zustandsmodifikation in Handler
volatile int counter = 0;
volatile int processing = 0;
void vulnerable_handler(int sig) {
// Anfällig: Nicht-atomares Lesen-Modifizieren-Schreiben
if (processing) {
counter++; // Kann mit Hauptprogramm racen
}
}
void process_data() {
processing = 1;
// Anfällig: Signal kann zwischen diesen Anweisungen ankommen
counter = 0;
// ... verarbeiten ...
int result = counter; // Kann unerwarteten Wert haben
processing = 0;
}
Korrigierter Code
// Korrigiert: Minimaler Signal-Handler mit Flag
#include <signal.h>
#include <stdlib.h>
#include <string.h>
volatile sig_atomic_t signal_received = 0;
void secure_handler(int sig) {
// Korrigiert: Nur Flag setzen - async-signal-sicher
signal_received = 1;
}
int main() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = secure_handler;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, NULL);
char *buffer = NULL;
while (1) {
// Korrigiert: Flag in Hauptschleife prüfen
if (signal_received) {
signal_received = 0;
// Signal sicher im Hauptkontext behandeln
if (buffer) free(buffer);
buffer = malloc(100);
}
// Normale Verarbeitung mit blockierten Signalen während kritischem Abschnitt
sigset_t block_set, old_set;
sigemptyset(&block_set);
sigaddset(&block_set, SIGINT);
sigprocmask(SIG_BLOCK, &block_set, &old_set);
// Kritischer Abschnitt - Signale blockiert
buffer = realloc(buffer, 1000);
sigprocmask(SIG_SETMASK, &old_set, NULL);
}
}
// Korrigiert: signalfd für synchrone Signalbehandlung verwenden (Linux)
#include <sys/signalfd.h>
#include <signal.h>
#include <unistd.h>
int main() {
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
// Signale blockieren, damit sie über signalfd geliefert werden
sigprocmask(SIG_BLOCK, &mask, NULL);
// signalfd erstellen
int sfd = signalfd(-1, &mask, 0);
while (1) {
struct signalfd_siginfo fdsi;
ssize_t s = read(sfd, &fdsi, sizeof(fdsi));
if (s == sizeof(fdsi)) {
// Korrigiert: Signal synchron behandeln - alle Funktionen sicher
printf("Signal %d empfangen\n", fdsi.ssi_signo);
// Kann hier sicher jede Funktion aufrufen
}
}
}
CVE-Beispiele
- CVE-1999-0035 — Signal-Handler ermöglichte Zugriff auf Dateien mit erhöhten Privilegien.
- CVE-2001-0905 — Signalunterbrechung führte zu Root-Ausführung.
- CVE-2004-0794 — Remote-SIGURG-Signal nutzte Handler-Logik aus.
- CVE-2004-2259 — SIGCHLD während malloc verursachte Abstürze.
Referenzen
- MITRE Corporation. "CWE-364: Signal Handler Race Condition." https://cwe.mitre.org/data/definitions/364.html
- Linux man-pages. "signal-safety(7)." https://man7.org/linux/man-pages/man7/signal-safety.7.html