Hardware-Logik mit unsicherer De-Synchronisation zwischen Steuer- und Datenkanalen
Beschreibung
Hardware-Logik mit unsicherer De-Synchronisation zwischen Steuer- und Datenkanalen tritt auf, wenn die Fehlerbehandlungs- und Sicherheitspruflogik der Hardware es erlaubt, dass Daten weitergeleitet werden, bevor Sicherheitsuberprüfungen abgeschlossen sind. Hochleistungs-On-Chip-Busprotokolle verwenden separate Steuer- und Datenkanale für Effizienz. Logikfehler können die Datenweiterleitung ermöglichen, bevor Sicherheitsprüfungen abgeschlossen sind. Wenn Angreifer diese Daten im Hardware-Zustand beobachten können, entstehen Vertraulichkeitsverletzungen. Die Meltdown-Schwachstelle ist ein Beispiel dafür - privilegierte Daten wurden spekulativ geladen und unabhängig von der Berechtigungsstufe zurückgegeben, wobei Spuren im mikroarchitektonischen Zustand trotz Ausnahmebehandlung verblieben.
Risiko
Steuer-/Daten-De-Synchronisation hat schwerwiegende Sicherheitsauswirkungen. Privilegierte Daten können spekulativ geleakt werden. Sicherheitsprüfungen können durch Timing umgangen werden. Cache-Seitenkanale können geschützte Daten offenbaren. Der mikroarchitektonische Zustand kann Geheimnisse aufbewahren. Transiente Ausführungsangriffe werden möglich. Speicherisolation kann verletzt werden. Kernel-Speicher kann aus dem Benutzerraum lesbar sein. Kryptografische Schlüssel können offengelegt werden.
Lösung
Uberprüfen Sie die Datenroutinglogik grundlich, um sicherzustellen, dass jede Fehlerbehandlung oder Sicherheitsprüfung illegale Datenflusse effektiv blockiert. Puffern Sie alle Daten, bis die Sicherheitsautorisierung abgeschlossen ist. Implementieren Sie Spekulationsbarrieren. Loschen Sie den mikroarchitektonischen Zustand bei Sicherheitsfehlern. Verwenden Sie zeitkonstante Implementierungen. Stellen Sie sicher, dass Steuer- und Datenpfade synchronisiert bleiben. Validieren Sie, dass Sicherheitsprüfungen vor der Datenweiterleitung abgeschlossen sind.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Umfang: Vertraulichkeit Speicher lesen - Geschutzte Daten können durch transiente Ausführung und Seitenkanalanalyse gelesen werden. |
| Vertraulichkeit | Umfang: Vertraulichkeit Anwendungsdaten lesen - Anwendungsgeheimnisse können durch mikroarchitektonische Seitenkanale durchsickern. |
Beispielcode und Lösung
Verwundbarer Code
// VERWUNDBAR: Datenweiterleitung vor Abschluss der Sicherheitsprüfung
module vulnerable_bus_bridge (
input wire clk,
input wire reset_n,
// Anfrage vom nicht vertrauenswurdigen CPU
input wire [31:0] req_addr,
input wire [31:0] req_data,
input wire req_read,
input wire req_write,
input wire req_valid,
// Firewall-Schnittstelle
output reg [31:0] fw_addr,
output reg fw_check_request,
input wire fw_access_allowed,
input wire fw_check_complete,
// Speicher-Schnittstelle
output reg [31:0] mem_addr,
output reg mem_read,
output reg mem_write,
input wire [31:0] mem_read_data,
// Antwort an CPU
output reg [31:0] resp_data,
output reg resp_valid
);
reg [31:0] pending_addr;
reg pending_read;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
mem_read <= 1'b0;
mem_write <= 1'b0;
resp_valid <= 1'b0;
end
else begin
// Firewall-Prüfung starten
if (req_valid) begin
fw_addr <= req_addr;
fw_check_request <= 1'b1;
pending_addr <= req_addr;
pending_read <= req_read;
end
// VERWUNDBAR: Leseanfrage an Speicher weiterleiten BEVOR Firewall fertig ist
// Dies geschieht aus Leistungsgrunden, erzeugt aber einen Seitenkanal
if (req_valid && req_read) begin
mem_addr <= req_addr;
mem_read <= 1'b1; // VERWUNDBAR: Spekulativer Lesevorgang
end
// Wenn Firewall fertig, Ergebnis prüfen
if (fw_check_complete) begin
if (fw_access_allowed) begin
// Zugriff erlaubt - Daten zurückgeben
resp_data <= mem_read_data;
resp_valid <= 1'b1;
end
else begin
// VERWUNDBAR: Daten bereits gelesen, möglicherweise im Cache
// Obwohl wir sie nicht zurückgeben, kann der Angreifer
// sie durch Cache-Timing beobachten
resp_data <= 32'h0;
resp_valid <= 1'b1;
end
mem_read <= 1'b0;
end
end
end
// Angriff (Meltdown-Stil):
// 1. Leseanfrage an geschützte Adresse
// 2. Speicherlesevorgang erfolgt spekulativ
// 3. Geschutzte Daten werden in den Cache geladen
// 4. Firewall verweigert Zugriff, Daten werden nicht zurückgegeben
// 5. Aber der Cache-Zustand offenbart die Daten durch Timing
endmodule
// VERWUNDBAR: Prozessor mit spekulativem Ausführungsleck
module vulnerable_speculative_load (
input wire clk,
input wire reset_n,
input wire [31:0] load_addr,
input wire load_request,
input wire [1:0] load_privilege,
input wire [1:0] current_privilege,
output reg [31:0] load_data,
output reg load_complete,
output reg load_fault
);
reg [31:0] speculative_data;
reg speculative_load_done;
reg privilege_check_done;
reg privilege_ok;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
speculative_load_done <= 1'b0;
privilege_check_done <= 1'b0;
load_complete <= 1'b0;
load_fault <= 1'b0;
end
else if (load_request) begin
// VERWUNDBAR: Daten spekulativ laden
speculative_data <= memory_read(load_addr);
speculative_load_done <= 1'b1;
// Berechtigungsprüfung erfolgt parallel, dauert aber langer
privilege_ok <= (current_privilege >= load_privilege);
privilege_check_done <= 1'b1;
end
else if (speculative_load_done && privilege_check_done) begin
if (privilege_ok) begin
load_data <= speculative_data;
load_complete <= 1'b1;
end
else begin
// VERWUNDBAR: Daten wurden bereits geladen und haben möglicherweise
// den Cache-Zustand beeinflusst bevor wir hierher gelangen
load_fault <= 1'b1;
load_data <= 32'h0;
end
speculative_load_done <= 1'b0;
privilege_check_done <= 1'b0;
end
end
endmodule
// VERWUNDBAR: CPU-Code anfällig für Meltdown
#include <stdint.h>
// Array des Angreifers für Cache-Seitenkanal
uint8_t probe_array[256 * 4096];
void vulnerable_meltdown_gadget(uint8_t* kernel_addr) {
// Dieser Code ist VERWUNDBAR für Meltdown-Angriffe
uint8_t kernel_byte;
// VERWUNDBAR: Spekulatives Lesen von Kernel-Speicher
// Obwohl dies einen Fehler auslöst, wird die spekulative Ausführung
// das Byte lesen und den Cache-Zustand beeinflussen
kernel_byte = *kernel_addr;
// VERWUNDBAR: Abhängiger Ladevorgang basierend auf Kernel-Byte
// Dies lädt probe_array[kernel_byte * 4096] in den Cache
volatile uint8_t dummy = probe_array[kernel_byte * 4096];
// Der Fehler tritt hier auf, aber der Cache-Zustand wurde bereits geleakt
}
// Angreifer kann dann Cache-Timing messen um kernel_byte zu bestimmen
void meltdown_attack(uint8_t* kernel_addr) {
// Probe-Array aus dem Cache loschen
for (int i = 0; i < 256; i++) {
_mm_clflush(&probe_array[i * 4096]);
}
// Verwundbares Gadget ausführen (wird fehlschlagen)
try_execute(vulnerable_meltdown_gadget, kernel_addr);
// Cache-Timing für jedes Probe-Array-Element messen
for (int i = 0; i < 256; i++) {
uint64_t start = rdtsc();
volatile uint8_t dummy = probe_array[i * 4096];
uint64_t time = rdtsc() - start;
if (time < CACHE_HIT_THRESHOLD) {
// Dieser Index war im Cache - das ist der Kernel-Byte-Wert!
printf("Kernel byte: %d\n", i);
}
}
}
Sichere Lösung
// SICHER: Daten blockiert bis Sicherheitsprüfung abgeschlossen
module secure_bus_bridge (
input wire clk,
input wire reset_n,
// Anfrage vom nicht vertrauenswurdigen CPU
input wire [31:0] req_addr,
input wire [31:0] req_data,
input wire req_read,
input wire req_write,
input wire req_valid,
// Firewall-Schnittstelle
output reg [31:0] fw_addr,
output reg fw_check_request,
input wire fw_access_allowed,
input wire fw_check_complete,
// Speicher-Schnittstelle
output reg [31:0] mem_addr,
output reg mem_read,
output reg mem_write,
input wire [31:0] mem_read_data,
// Antwort an CPU
output reg [31:0] resp_data,
output reg resp_valid
);
// Zustandsmaschine
parameter IDLE = 2'd0;
parameter WAIT_FIREWALL = 2'd1;
parameter DO_ACCESS = 2'd2;
parameter RESPOND = 2'd3;
reg [1:0] state;
reg [31:0] pending_addr;
reg pending_read;
reg pending_write;
reg [31:0] pending_data;
reg access_allowed_reg;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
state <= IDLE;
mem_read <= 1'b0;
mem_write <= 1'b0;
resp_valid <= 1'b0;
fw_check_request <= 1'b0;
end
else begin
case (state)
IDLE: begin
resp_valid <= 1'b0;
if (req_valid) begin
// Anfrage speichern
pending_addr <= req_addr;
pending_read <= req_read;
pending_write <= req_write;
pending_data <= req_data;
// Firewall-Prüfung starten
fw_addr <= req_addr;
fw_check_request <= 1'b1;
state <= WAIT_FIREWALL;
// SICHER: Noch KEINEN Speicherzugriff durchführen
mem_read <= 1'b0;
mem_write <= 1'b0;
end
end
WAIT_FIREWALL: begin
fw_check_request <= 1'b0;
if (fw_check_complete) begin
access_allowed_reg <= fw_access_allowed;
if (fw_access_allowed) begin
// SICHER: Speicher nur NACH Firewall-Genehmigung zugreifen
mem_addr <= pending_addr;
mem_read <= pending_read;
mem_write <= pending_write;
state <= DO_ACCESS;
end
else begin
// SICHER: Kein Speicherzugriff für abgelehnte Anfragen
state <= RESPOND;
end
end
end
DO_ACCESS: begin
// Auf Speicheroperation warten
mem_read <= 1'b0;
mem_write <= 1'b0;
state <= RESPOND;
end
RESPOND: begin
if (access_allowed_reg) begin
resp_data <= mem_read_data;
end
else begin
resp_data <= 32'h0;
end
resp_valid <= 1'b1;
state <= IDLE;
end
endcase
end
end
endmodule
// SICHER: Prozessor mit serialisierter Berechtigungsprüfung
module secure_load_unit (
input wire clk,
input wire reset_n,
input wire [31:0] load_addr,
input wire load_request,
input wire [1:0] addr_privilege, // Erforderliche Berechtigung für Adresse
input wire [1:0] current_privilege, // Aktuelle Ausführungsberechtigung
output reg [31:0] load_data,
output reg load_complete,
output reg load_fault
);
parameter IDLE = 2'd0;
parameter CHECK_PRIVILEGE = 2'd1;
parameter DO_LOAD = 2'd2;
parameter COMPLETE = 2'd3;
reg [1:0] state;
reg [31:0] saved_addr;
reg privilege_ok;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
state <= IDLE;
load_complete <= 1'b0;
load_fault <= 1'b0;
end
else begin
case (state)
IDLE: begin
load_complete <= 1'b0;
load_fault <= 1'b0;
if (load_request) begin
saved_addr <= load_addr;
state <= CHECK_PRIVILEGE;
end
end
CHECK_PRIVILEGE: begin
// SICHER: Berechtigungsprüfung VOR jedem Speicherzugriff abschließen
privilege_ok = (current_privilege >= addr_privilege);
if (privilege_ok) begin
state <= DO_LOAD;
end
else begin
// SICHER: Fehler ohne jemals den Speicher zu beruhren
load_fault <= 1'b1;
load_data <= 32'h0;
state <= COMPLETE;
end
end
DO_LOAD: begin
// SICHER: Nur hierher gelangen wenn Berechtigungsprüfung bestanden
load_data <= memory_read(saved_addr);
load_complete <= 1'b1;
state <= COMPLETE;
end
COMPLETE: begin
state <= IDLE;
end
endcase
end
end
endmodule
// SICHER: Spekulative Ausführung mit Barrieren
module secure_speculative_unit (
input wire clk,
input wire reset_n,
input wire [31:0] addr,
input wire load_request,
input wire [1:0] required_privilege,
input wire [1:0] current_privilege,
output reg [31:0] data,
output reg complete,
output reg fault
);
// SICHER: Spekulationsbarriere verwenden
reg speculation_allowed;
reg [31:0] speculative_result;
reg privilege_verified;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
speculation_allowed <= 1'b0;
complete <= 1'b0;
fault <= 1'b0;
end
else begin
// SICHER: Berechtigungsprüfung steuert Spekulation
privilege_verified <= (current_privilege >= required_privilege);
// SICHER: Spekulative Ausführung nur erlauben wenn Berechtigung verifiziert
speculation_allowed <= privilege_verified;
if (load_request && speculation_allowed) begin
speculative_result <= memory_read(addr);
complete <= 1'b1;
data <= speculative_result;
end
else if (load_request && !speculation_allowed) begin
// SICHER: Spekulativen Zugriff auf geschützten Speicher blockieren
fault <= 1'b1;
data <= 32'h0;
end
end
end
// SICHER: Mikroarchitektonischen Zustand bei Fehler loschen
always @(posedge clk) begin
if (fault) begin
// Spekulativen Zustand loschen
flush_speculation_state();
end
end
endmodule
// SICHER: Meltdown-Gegenmaßnahmen
#include <stdint.h>
// SICHER: Kernel Page Table Isolation (KPTI)
// Kernel-Seiten sind nicht in Benutzer-Seitentabellen gemappt
// Verhindert spekulativen Zugriff auf Kernel-Speicher
void setup_kpti(void) {
// Separate Seitentabellen für Benutzer- und Kernel-Modus erstellen
// Benutzer-Seitentabellen enthalten keine Kernel-Zuordnungen
user_pgd = create_user_page_tables();
kernel_pgd = create_kernel_page_tables();
// Bei Syscall-Eintritt zu Kernel-Seitentabellen wechseln
// Bei Syscall-Austritt zu Benutzer-Seitentabellen wechseln
}
// SICHER: Spekulation an Berechtigungsgrenzen serialisieren
void secure_syscall_entry(void) {
// SICHER: Spekulationsbarriere vor Zugriff auf Kernel-Daten
__asm__ volatile("lfence" ::: "memory");
// Jetzt sicher auf Kernel-Speicher zuzugreifen
}
// SICHER: Mikroarchitektonischen Zustand loschen
void secure_syscall_exit(void) {
// L1-Daten-Cache loschen
flush_l1d_cache();
// Branch-Prediction-Zustand loschen
clear_branch_history();
// Spekulationsbarriere
__asm__ volatile("lfence" ::: "memory");
// Zu Benutzer-Seitentabellen wechseln
switch_to_user_pgd();
}
// SICHER: Grenzenprüfung mit Spekulationsbarriere
int secure_array_access(int index, int* array, int array_size) {
// Grenzen prüfen
if (index >= array_size) {
return -1;
}
// SICHER: Spekulationsbarriere nach Grenzenprüfung
// Verhindert spekulativen Zugriff über die Prüfung hinaus
__asm__ volatile("lfence" ::: "memory");
// Jetzt sicher zuzugreifen
return array[index];
}
CVE-Beispiele
- CVE-2017-5754 (Meltdown): Mikroprozessoren mit spekulativer Ausführung ermöglichten unbefugte Informationsoffenlegung über Cache-Seitenkanalanalyse
Verwandte CWEs
- CWE-821: Fehlerhafte Synchronisation (ubergeordnet)
- CWE-1037: Entfernung oder Modifikation sicherheitskritischen Codes durch Prozessoroptimierung (verwandt)
- CAPEC-233: Berechtigungserweiterung (Angriffsmuster)
- CAPEC-663: Ausnutzung transienter Befehlsausführung (Angriffsmuster)
Referenzen
- MITRE Corporation. "CWE-1264: Hardware Logic with Insecure De-Synchronization between Control and Data Channels." https://cwe.mitre.org/data/definitions/1264.html
- Lipp et al. "Meltdown: Reading Kernel Memory from User Space"
- Intel. "Speculative Execution Side Channel Mitigations"