Informationsoffenlegung durch mikroarchitekturellen Zustand nach transienter Ausführung

Beschreibung

Informationsoffenlegung durch mikroarchitekturellen Zustand nach transienter Ausführung tritt auf, wenn ein Prozessor den mikroarchitekturellen Zustand nach fehlerhaften Mikrocode-Assists oder spekulativer Ausführung nicht ordnungsgemäß bereinigt, was zu transienter Ausführung führt. Wenn Ausnahmen, Fehlspekulationen oder Mikrocode-Assists auftreten, spülen Prozessoren typischerweise Ergebnisse, um eine Kontamination des architekturellen Zustands zu verhindern. Jedoch bleiben Spuren in mikroarchitekturellen Puffern, die Angreifer über Seitenkanal-Analyse ausnutzen können. Load Value Injection (LVI) veranschaulicht dies, indem fehlerhafte Werte in Zwischenpuffer injiziert werden. Erfolgreiche Angriffe erfordern, dass fehlerhafte transiente Ausführung sensible Datenspuren hinterlässt, der Angreifer mikroarchitekturelle Ausnahmen provozieren kann und ausbeutbare Opfer-Code-Operationen identifiziert werden.

Risiko

Schwachstellen durch transiente Ausführung haben schwerwiegende Auswirkungen. Beliebiges Speicherlesen über Seitenkanale. Prozessübergreifende Informationslecks. Ausbruch aus virtuellen Maschinen. Offenlegung von Kernel-Speicher. Extraktion kryptographischer Schlüssel. Passwortoffenlegung. Hohe Komplexität aber hohe Auswirkung bei Ausnutzung. Betrifft die meisten modernen Prozessoren mit spekulativer Ausführung.

Lösung

Sicherstellen, dass Prozessoren illegale Datenflüsse von fehlerhaften Micro-Ops auf mikroarchitektureller Ebene während des Hardware-Designs verhindern (hohe Wirksamkeit mit begrenztem Leistungseinfluss). Memory-Fence-Instruktionen (lfence, sfence, mfence, clflush) einfügen, um Berechnungsspuren während der Kompilierung zu entfernen und sequentielle Speicherzugriffsabschluss zu erzwingen (hohe Wirksamkeit aber signifikanter Leistungseinfluss). Mikrocode-Updates anwenden. Seitentabellen-Isolationstechniken verwenden.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Informationsoffenlegung durch mikroarchitekturelle Seitenkanale, die sensible Daten preisgeben.
IntegritätBereich: Integrität

Potenzielle Speichermodifikation durch Manipulation transienter Ausführung.

Beispielcode

Verwundbarer Code

// VERWUNDBAR: Code-Muster, die anfällig für Angriffe durch transiente Ausführung sind

#include <stdint.h>
#include <string.h>

// Seitenkanal-Probe-Array
#define CACHE_LINE_SIZE 64
#define PROBE_ARRAY_SIZE (256 * CACHE_LINE_SIZE)
uint8_t probe_array[PROBE_ARRAY_SIZE];

// VERWUNDBAR: Grenzprüfung kann durch Spekulation umgangen werden
void vulnerable_spectre_v1(uint8_t* array, size_t array_size,
                           size_t untrusted_index, uint8_t* secret) {

    // Grenzprüfung - aber CPU kann darüber hinaus spekulieren
    if (untrusted_index < array_size) {
        // VERWUNDBAR: Spekulative Ausführung liest außerhalb der Grenzen
        uint8_t value = array[untrusted_index];

        // VERWUNDBAR: Abhängiges Laden erzeugt Cache-Seitenkanal
        uint8_t probe = probe_array[value * CACHE_LINE_SIZE];

        // Selbst wenn Grenzprüfung fehlschlägt, hat spekulative Ausführung
        // bereits geheime Daten geladen und den Cache beeinflusst
    }

    // Angriff:
    // 1. Branch-Predictor trainieren zu erwarten, dass untrusted_index < array_size
    // 2. Index außerhalb der Grenzen übergeben, der auf Geheimnis zeigt
    // 3. CPU liest spekulativ das Geheimnis
    // 4. Cache-Zustand verrät Geheimnis durch Timing-Analyse
}

// VERWUNDBAR: Indirekter Branch anfällig für Spectre v2
typedef void (*func_ptr)(uint8_t*);

void vulnerable_spectre_v2(func_ptr* vtable, int index, uint8_t* data) {
    // VERWUNDBAR: Indirekter Branch kann fehlvorhergesagt werden
    func_ptr func = vtable[index];

    // VERWUNDBAR: Angreifer kann Branch-Target-Buffer vergiften
    func(data);

    // Angriff injiziert Gadget-Adresse, die Daten leakt
}

// VERWUNDBAR: Meltdown-artiges Lesen von Kernel-Speicher
uint8_t vulnerable_meltdown_read(void* kernel_address) {
    uint8_t value;

    // Dies wird einen Fehler verursachen - aber zuerst spekulativ ausführen
    // VERWUNDBAR: Transientes Lesen von Kernel-Speicher
    value = *(uint8_t*)kernel_address;

    // VERWUNDBAR: Geheimnis-abhängiger Cache-Zugriff
    uint8_t probe = probe_array[value * CACHE_LINE_SIZE];

    return value;  // Wird wegen Fehler nie erreicht

    // Angriff: probe_array-Zugriffszeiten messen, um Wert zu bestimmen
}

// VERWUNDBAR: Load Value Injection (LVI)
void vulnerable_lvi(uint8_t* trusted_ptr, size_t* untrusted_size) {
    // VERWUNDBAR: Fehlerhaftes Laden kann Angreiferwert injizieren
    size_t size = *untrusted_size;  // Kann fehlen

    // VERWUNDBAR: Injizierte Größe in Grenzen verwendet
    for (size_t i = 0; i < size; i++) {
        // Wenn fehlerhaftes Laden großen Größenwert injiziert,
        // liest dies über die Grenzen von trusted_ptr hinaus
        process_byte(trusted_ptr[i]);
    }
}

// VERWUNDBAR: Microarchitectural Data Sampling (MDS)
void vulnerable_mds_leak() {
    // VERWUNDBAR: Sensible Daten in CPU-Puffern
    char password[64];
    get_password(password);

    // Passwort für Authentifizierung verwenden
    authenticate(password);

    // Passwort löschen
    memset(password, 0, sizeof(password));

    // VERWUNDBAR: Passwort kann sich noch befinden in:
    // - Store-Puffern
    // - Fill-Puffern
    // - Load-Ports
    // Angreifer auf demselben Kern kann diese Puffer abtasten
}
; VERWUNDBAR: Assembly mit Problemen der spekulativen Ausführung

; VERWUNDBAR: Keine Spekulationsbarriere nach Grenzprüfung
vulnerable_array_access:
    cmp     rdi, rsi            ; Grenzen prüfen
    jae     .out_of_bounds      ; Sprung wenn >= array_size

    ; VERWUNDBAR: Spekulative Ausführung geht hier weiter
    ; auch wenn Grenzprüfung fehlschlagen wird
    mov     al, [r8 + rdi]      ; Potenziell außerhalb der Grenzen lesen

    ; VERWUNDBAR: Erzeugt Cache-Seitenkanal
    shl     rax, 12             ; Mit Seitengröße multiplizieren
    mov     bl, [r9 + rax]      ; Probe-Array-Zugriff

.out_of_bounds:
    ret

; VERWUNDBAR: Keine Barriere nach indirektem Branch
vulnerable_indirect_call:
    ; VERWUNDBAR: Branch-Target kann fehlvorhergesagt werden
    call    [rax]               ; Indirekter Aufruf

    ; Angreifer-kontrolliertes Gadget könnte transient ausgeführt worden sein
    ret

Sichere Lösung

// SICHER: Gegenmaßnahmen für Schwachstellen durch transiente Ausführung

#include <stdint.h>
#include <string.h>
#include <x86intrin.h>

// SICHER: Compiler-Barriere und lfence verwenden
#define speculation_barrier() \
    do { \
        _mm_lfence(); \
    } while(0)

// SICHER: Grenzprüfung mit Spekulationsbarriere
void safe_array_access(uint8_t* array, size_t array_size,
                       size_t untrusted_index, uint8_t* out) {

    // Grenzprüfung
    if (untrusted_index < array_size) {
        // SICHER: Serialisierende Instruktion verhindert Spekulation
        speculation_barrier();

        // Jetzt sicher - Spekulation gestoppt
        *out = array[untrusted_index];
    }
}

// SICHER: Index-Maskierung statt Verzweigung verwenden
void safe_array_access_masked(uint8_t* array, size_t array_size,
                              size_t untrusted_index, uint8_t* out) {
    // SICHER: Index auf gültigen Bereich maskieren
    // Dies funktioniert, weil Spekulation weiterhin den maskierten Wert verwendet
    size_t safe_index = untrusted_index & (array_size - 1);

    // Zusätzliche Prüfung für Nicht-Zweierpotenz-Größen
    size_t mask = (untrusted_index < array_size) ? ~0UL : 0;
    safe_index &= mask;

    *out = array[safe_index];
}

// SICHER: Indirekter Branch-Schutz (Retpoline)
// Statt: call [rax]
// Retpoline-Thunk verwenden
extern void __x86_indirect_thunk_rax(void);

void safe_indirect_call(void (*func_ptr)(void*), void* arg) {
    // SICHER: Retpoline für indirekte Aufrufe verwenden
    // Compiler generiert sichere indirekte Branch-Sequenz

    // Mit -mindirect-branch=thunk konvertiert der Compiler zu:
    // call __x86_indirect_thunk_rax
    // Was Branch-Target-Injection verhindert
    func_ptr(arg);
}

// SICHER: Schutz gegen Meltdown mit KPTI
// (Auf OS-Ebene implementiert - Kernel Page Table Isolation)
// User Space kann nicht auf Kernel-Speicher zugreifen, auch nicht spekulativ,
// weil Kernel-Seiten nicht in User-Seitentabellen eingetragen sind

// SICHER: Mikroarchitekturellen Zustand bereinigen
void safe_sensitive_operation(uint8_t* sensitive_data, size_t size) {
    // Sensible Daten verarbeiten
    process_data(sensitive_data, size);

    // SICHER: CPU-Puffer mit VERW-Instruktion bereinigen
    // (Auf unterstützten Prozessoren)
    #ifdef HAS_MDS_MITIGATION
    unsigned short ds = 0;
    __asm__ volatile("verw %0" : : "m"(ds) : "cc");
    #endif

    // SICHER: Speicher bereinigen
    explicit_bzero(sensitive_data, size);

    // SICHER: Memory Fence zur Sicherstellung des Abschlusses
    _mm_mfence();
}

// SICHER: LVI-Gegenmaßnahme mit lfence
void safe_lvi_protected(uint8_t* trusted_ptr, size_t* untrusted_size) {
    // SICHER: LFENCE nach jedem Laden aus nicht vertrauenswürdiger Quelle
    size_t size = *untrusted_size;
    _mm_lfence();  // LVI verhindern

    // SICHER: Auch den Wert validieren
    if (size > MAX_ALLOWED_SIZE) {
        size = MAX_ALLOWED_SIZE;
    }

    for (size_t i = 0; i < size; i++) {
        uint8_t byte = trusted_ptr[i];
        _mm_lfence();  // SICHER: LFENCE nach jedem Laden
        process_byte(byte);
    }
}

// SICHER: Vergleich in konstanter Zeit zur Verhinderung von Timing-Seitenkanalen
int safe_constant_time_compare(const uint8_t* a, const uint8_t* b, size_t len) {
    uint8_t result = 0;

    // SICHER: Alle Bytes vergleichen unabhängig von Unterschieden
    for (size_t i = 0; i < len; i++) {
        result |= a[i] ^ b[i];
    }

    // SICHER: Memory Fence vor der Rückgabe
    _mm_lfence();

    return result == 0;
}
; SICHER: Assembly mit Spekulationsbarrieren

; SICHER: Grenzprüfung mit LFENCE
safe_array_access:
    cmp     rdi, rsi            ; Grenzen prüfen
    jae     .out_of_bounds      ; Sprung wenn >= array_size

    ; SICHER: Serialisierende Instruktion
    lfence                      ; Spekulative Ausführung verhindern

    ; Jetzt sicher zum Zugriff
    mov     al, [r8 + rdi]      ; Innerhalb der Grenzen lesen
    ret

.out_of_bounds:
    xor     eax, eax
    ret

; SICHER: Retpoline für indirekte Aufrufe
__x86_indirect_thunk_rax:
    call    .setup_target
.capture_speculation:
    pause
    lfence
    jmp     .capture_speculation
.setup_target:
    mov     [rsp], rax          ; Rücksprungadresse überschreiben
    ret                         ; "Zurückkehren" zum Ziel

; SICHER: Sicherer indirekter Aufruf mittels Retpoline
safe_indirect_call:
    ; Ziel in rax laden
    mov     rax, [rdi]
    ; Retpoline verwenden
    jmp     __x86_indirect_thunk_rax
// SICHER: Compiler-Flags und Build-Optionen

/*
 * Empfohlene Compiler-Flags für Gegenmaßnahmen gegen transiente Ausführung:
 *
 * GCC/Clang:
 *   -mindirect-branch=thunk        # Retpoline für indirekte Branches
 *   -mfunction-return=thunk        # Retpoline für Returns
 *   -mindirect-branch-register     # Register für indirekte Branches verwenden
 *   -mspeculative-load-hardening   # Spekulative Loads härten (Clang)
 *   -fcf-protection=full           # Control-Flow-Enforcement (CET)
 *
 * MSVC:
 *   /Qspectre                      # Spectre-Gegenmaßnahmen
 *   /guard:cf                      # Control Flow Guard
 *
 * Kernel-Builds:
 *   CONFIG_PAGE_TABLE_ISOLATION=y  # KPTI (Meltdown)
 *   CONFIG_RETPOLINE=y             # Retpoline (Spectre v2)
 *   CONFIG_MICROCODE=y             # CPU-Mikrocode-Updates
 */

// Build-Zeit-Assertions für Gegenmaßnahmen
#if !defined(__HAVE_SPECULATION_BARRIER)
#error "Spekulationsbarriere nicht verfügbar - Gegenmaßnahmen erforderlich"
#endif

CVE-Beispiele

  • CVE-2017-5754: Meltdown - Rogue Data Cache Load, das Lesen von Kernel-Speicher aus dem User Space ermöglicht.
  • CVE-2017-5753: Spectre v1 - Bounds Check Bypass über spekulative Ausführung.
  • CVE-2017-5715: Spectre v2 - Branch Target Injection, die prozessübergreifende Angriffe ermöglicht.
  • CVE-2020-0551: Load Value Injection (LVI), die Angriffe durch transiente Ausführung ermöglicht.
  • CVE-2018-12126: Microarchitectural Store Buffer Data Sampling (MSBDS/Fallout).
  • CVE-2019-11091: Microarchitectural Data Sampling Uncacheable Memory (MDSUM).

Verwandte CWEs

  • CWE-226: Sensitive Information in Resource Not Removed Before Reuse (übergeordnet)
  • CWE-1201: Core and Compute Issues (Kategorie)
  • CWE-203: Observable Discrepancy (verwandt - Seitenkanale)

Referenzen

  1. MITRE Corporation. "CWE-1342: Information Exposure through Microarchitectural State after Transient Execution." https://cwe.mitre.org/data/definitions/1342.html
  2. Intel. "Speculative Execution Side Channel Mitigations"
  3. AMD. "Software Techniques for Managing Speculation"
  4. Google Project Zero. "Spectre and Meltdown"