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

AuswirkungDetails
VertraulichkeitUmfang: Vertraulichkeit

Speicher lesen - Geschutzte Daten können durch transiente Ausführung und Seitenkanalanalyse gelesen werden.
VertraulichkeitUmfang: 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

  1. MITRE Corporation. "CWE-1264: Hardware Logic with Insecure De-Synchronization between Control and Data Channels." https://cwe.mitre.org/data/definitions/1264.html
  2. Lipp et al. "Meltdown: Reading Kernel Memory from User Space"
  3. Intel. "Speculative Execution Side Channel Mitigations"