Signal-Handler-Funktion mit mehreren Signalen assoziiert

Beschreibung

Signal-Handler-Funktion mit mehreren Signalen assoziiert ist eine Nebenläufigkeits-Schwachstelle, bei der eine einzelne Funktion als Handler für mehrere verschiedene Signale registriert wird. Obwohl dieser Ansatz effizient erscheinen mag, erzeugt er potenzielle Sicherheitsprobleme, wenn der Handler geteilten Zustand verwendet, nicht-reentrant Funktionen aufruft oder Seiteneffekte hat. Wenn ein Handler als Reaktion auf ein Signal ausgeführt wird, kann ein anderes Signal, das mit demselben Handler assoziiert ist, ihn unterbrechen und dazu führen, dass der Handler rekursiv aufgerufen wird. Dies kann zu Race Conditions, Zustandsbeschädigung, Double-Free-Schwachstellen oder anderem undefinierten Verhalten führen, das Angreifer ausnutzen können.

Risiko

Wenn ein Handler ein Signal verarbeitet und von einem anderen Signal unterbrochen wird, das denselben Handler auslöst, werden der geteilte Zustand und die Ressourcen beschädigt. Globale Variablen können in inkonsistenten Zuständen hinterlassen werden. Speicher, der während des ersten Aufrufs freigegeben wurde, kann erneut freigegeben werden (Double-Free). Nicht-reentrant Funktionen wie malloc() oder syslog() können ihre internen Datenstrukturen beschädigen, wenn sie rekursiv aufgerufen werden. Angreifer, die mehrere Signale an einen Prozess senden können, können ihre Angriffe timen, um diese Race Conditions auszulösen und möglicherweise Denial of Service oder Codeausführung zu erreichen. Die Schwachstelle ist besonders gefährlich, weil dieselbe Handler-Funktion beide Signal-Aufrufe verarbeitet.

Lösung

Verwenden Sie separate Handler-Funktionen für verschiedene Signale, die jeweils ihre spezifischen Belange behandeln. Wenn mehrere Signale Logik teilen müssen, extrahieren Sie diese in eine Hilfsfunktion, die die Handler aufrufen, aber stellen Sie sicher, dass die Hilfsfunktion asynchron-signal-sicher und reentrant ist. Blockieren Sie verwandte Signale während ein Handler ausgeführt wird, mit sigprocmask() oder Signalmasken. Entwerfen Sie Handler so, dass sie nur ein Flag setzen, das die Hauptprogrammschleife prüft, anstatt komplexe Operationen direkt auszuführen. Verwenden Sie den Self-Pipe-Trick oder signalfd(), um Signale in Dateideskriptor-Ereignisse zu konvertieren, die in einem normalen Ausführungskontext behandelt werden können. Vermeiden Sie die Verwendung nicht-reentrant Funktionen in jedem Signal-Handler.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitBereich: Verfügbarkeit

DoS: Absturz, Beendigung oder Neustart - Zustandsbeschädigung durch reentrant Handler-Aufrufe verursacht Abstürze oder Hänger.
Integrität, Vertraulichkeit, VerfügbarkeitBereich: Integrität, Vertraulichkeit, Verfügbarkeit

Unerlaubten Code oder Befehle ausführen - Beschädigung von Heap-Metadaten oder Funktionszeigern kann Codeausführung ermöglichen.
VertraulichkeitBereich: Vertraulichkeit

Anwendungsdaten lesen - Race Conditions können sensible Daten durch inkonsistenten Zustand preisgeben.
ZugriffskontrolleBereich: Zugriffskontrolle

Privilegien erlangen oder Identität annehmen - Beschädigung von sicherheitskritischem Zustand kann Privilegieneskalation ermöglichen.

Beispielcode

Anfälliger Code

// Anfällig: Einzelner Handler für mehrere Signale
#include <signal.h>
#include <syslog.h>
#include <stdlib.h>

char *logMessage;

void handler(int sigNum) {
    // Anfällig: Verwendet globale Variable
    // Anfällig: Ruft nicht-reentrant syslog() auf
    syslog(LOG_NOTICE, "%s\n", logMessage);

    // Anfällig: Kann mitten im free unterbrochen werden
    free(logMessage);
    logMessage = NULL;

    // Anfällig: exit() ist nicht asynchron-signal-sicher
    exit(0);
}

int main(int argc, char* argv[]) {
    logMessage = strdup("Programm wird beendet");

    // Anfällig: Derselbe Handler für zwei Signale
    signal(SIGHUP, handler);
    signal(SIGTERM, handler);

    /* Angriffsszenario:
     * 1. SIGHUP kommt an, Handler startet
     * 2. syslog() ruft intern malloc() auf
     * 3. SIGTERM kommt an bevor malloc() abgeschlossen
     * 4. Derselbe Handler wird erneut aufgerufen
     * 5. Zweiter syslog()-Aufruf beschädigt Heap-Metadaten
     * Oder: Double-free von logMessage
     */

    while (1) {
        // Hauptschleife
    }

    return 0;
}
// Anfällig: Zähler-Inkrement ist nicht atomar
#include <signal.h>

int signal_count = 0;  // Geteilt zwischen Handler-Aufrufen

void counter_handler(int sig) {
    // Anfällig: Read-Modify-Write ist nicht atomar
    signal_count++;  // SIGUSR1 liest 5, SIGUSR2 liest auch 5, beide schreiben 6

    // Wenn dieser Handler Cleanup basierend auf Count macht...
    if (signal_count >= 3) {
        cleanup();  // Kann mit falschem Count aufgerufen werden
    }
}

int main() {
    signal(SIGUSR1, counter_handler);
    signal(SIGUSR2, counter_handler);

    while (1) { /* arbeiten */ }
}

Korrigierter Code

// Korrigiert: Separate Handler für jedes Signal
#include <signal.h>
#include <unistd.h>
#include <string.h>

volatile sig_atomic_t hup_received = 0;
volatile sig_atomic_t term_received = 0;

void hup_handler(int sigNum) {
    // Korrigiert: Nur Flag für dieses spezifische Signal setzen
    hup_received = 1;
}

void term_handler(int sigNum) {
    // Korrigiert: Separater Handler, separates Flag
    term_received = 1;
}

int main(int argc, char* argv[]) {
    signal(SIGHUP, hup_handler);
    signal(SIGTERM, term_handler);

    while (!hup_received && !term_received) {
        // Hauptschleife
    }

    // Korrigiert: Cleanup im Hauptkontext
    if (hup_received) {
        syslog(LOG_NOTICE, "SIGHUP empfangen, lade Konfiguration neu");
        reload_config();
    }
    if (term_received) {
        syslog(LOG_NOTICE, "SIGTERM empfangen, fahre herunter");
        cleanup_and_exit();
    }

    return 0;
}
// Korrigiert: Signale während Handler-Ausführung blockieren
#include <signal.h>

void safe_handler(int sig) {
    sigset_t mask, oldmask;

    // Korrigiert: Alle verwandten Signale während Handler blockieren
    sigemptyset(&mask);
    sigaddset(&mask, SIGHUP);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTERM);
    sigprocmask(SIG_BLOCK, &mask, &oldmask);

    // Jetzt sicher vor Reentranz durch diese Signale
    // Sollte immer noch nur asynchron-signal-sichere Operationen durchführen

    const char *msg = "Signal behandelt\n";
    write(STDERR_FILENO, msg, strlen(msg));

    // Signalmaske wiederherstellen
    sigprocmask(SIG_SETMASK, &oldmask, NULL);
}

int main() {
    struct sigaction sa;
    sigset_t mask;

    // Korrigiert: sigaction mit sa_mask zum automatischen Blockieren verwenden
    sigemptyset(&mask);
    sigaddset(&mask, SIGHUP);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTERM);

    sa.sa_handler = safe_handler;
    sa.sa_mask = mask;  // Diese Signale während Handler blockieren
    sa.sa_flags = 0;

    sigaction(SIGHUP, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);
    sigaction(SIGTERM, &sa, NULL);

    while (1) { /* arbeiten */ }
}
// Korrigiert: signalfd für einheitliche Signal-Behandlung verwenden
#include <signal.h>
#include <sys/signalfd.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    sigset_t mask;
    int sfd;
    struct signalfd_siginfo fdsi;

    // Signale blockieren, damit sie via signalfd geliefert werden
    sigemptyset(&mask);
    sigaddset(&mask, SIGHUP);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTERM);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    // signalfd erstellen
    sfd = signalfd(-1, &mask, 0);
    if (sfd == -1) {
        perror("signalfd");
        exit(1);
    }

    // Korrigiert: Signale im normalen Kontext via Dateideskriptor behandeln
    while (1) {
        ssize_t s = read(sfd, &fdsi, sizeof(fdsi));
        if (s != sizeof(fdsi)) {
            perror("read");
            continue;
        }

        // Sicher alles hier zu machen - wir sind nicht in einem Signal-Handler
        switch (fdsi.ssi_signo) {
            case SIGHUP:
                syslog(LOG_NOTICE, "Lade Konfiguration neu");
                reload_config();
                break;
            case SIGINT:
            case SIGTERM:
                syslog(LOG_NOTICE, "Fahre herunter");
                cleanup();
                close(sfd);
                exit(0);
        }
    }

    return 0;
}

Verwandte CWEs

  • CWE-364: Signal-Handler Race Condition (Eltern)
  • CWE-828: Signal-Handler mit nicht asynchron-sicherer Funktionalität (verwandt)
  • CWE-479: Signal-Handler-Verwendung einer nicht-reentrant Funktion (verwandt)
  • CWE-662: Unzureichende Synchronisation (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-831: Signal Handler Function Associated with Multiple Signals." https://cwe.mitre.org/data/definitions/831.html
  2. CERT C Secure Coding Standard. "SIG30-C. Call only asynchronous-safe functions within signal handlers."
  3. CERT C Secure Coding Standard. "SIG31-C. Do not access shared objects in signal handlers."