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
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Informationsoffenlegung durch mikroarchitekturelle Seitenkanale, die sensible Daten preisgeben. |
| Integrität | Bereich: 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
- MITRE Corporation. "CWE-1342: Information Exposure through Microarchitectural State after Transient Execution." https://cwe.mitre.org/data/definitions/1342.html
- Intel. "Speculative Execution Side Channel Mitigations"
- AMD. "Software Techniques for Managing Speculation"
- Google Project Zero. "Spectre and Meltdown"