Nicht-transparente Nutzung gemeinsamer mikroarchitektureller Ressourcen

Beschreibung

Die nicht-transparente Nutzung gemeinsamer mikroarchitektureller Ressourcen tritt auf, wenn Hardware-Strukturen, die über Ausführungskontexte hinweg geteilt werden (z.B. Caches und Branch Predictors), die erwartete Architektur-Isolation zwischen Kontexten verletzen. Moderne Prozessoren verwenden Techniken zur Leistungsoptimierung wie Out-of-Order-Ausführung, Spekulation und Caching, die für Programmierer transparent arbeiten. Wenn Hardware-Implementierungen diese Ressourcen über isolierte Ausführungskontexte hinweg teilen, entstehen verdeckte Kanäle, die von Angreifern ausgenutzt werden können. Zu den spezifisch verwundbaren gemeinsam genutzten Ressourcen gehören Caches, Branch-Prediction-Logik und Load/Store-Buffer. Die Kombination aus spekulativer Ausführung und Out-of-Order-Verarbeitung verstärkt die Kontrolle des Angreifers über Datenlecks durch diese Kanäle.

Risiko

Die nicht-transparente gemeinsame Ressourcennutzung hat schwerwiegende Sicherheitsauswirkungen. Kryptografische Schlüssel können durch Cache-Timing extrahiert werden. ASLR-Offsets werden über Branch Predictors offengelegt. Beliebige Speicherinhalte sind lesbar. Prozessübergreifende Informationsoffenlegung. Spekulative Ausführungsangriffe (Spectre, Meltdown). Verdeckte Kanäle zwischen Sicherheitsdomänen. Undokumentierte gemeinsame Nutzung macht den Schutz extrem schwierig.

Lösung

Implementieren Sie partitionierte Caches, um kontextübergreifende Datenfreigabe während der Architektur- und Entwurfsphase zu verhindern. Stellen Sie neue Barriere- und Flush-Befehle für die Cache-Steuerung bereit. Deaktivieren Sie hochauflösende Leistungszähler und Timer, die Timing-Seitenkanale ermöglichen. Implementieren Sie Barrieren für spekulative Ausführung. Verwenden Sie separaten Branch-Prediction-Status pro Sicherheitsdomäne. Erwägen Sie Hardware-Partitionierung gemeinsam genutzter Ressourcen.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitBereich: Vertraulichkeit

Sensible Informationen einschließlich kryptografischer Schlüssel, ASLR-Offsets und beliebiger Speicherinhalte sind durch mikroarchitekturelle Seitenkanale extrahierbar.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Code anfällig für Spectre-artige Angriffe

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

// VERWUNDBAR: Array-Grenzenprüfung kann durch spekulative Ausführung umgangen werden
uint8_t array1[16];
uint8_t array2[256 * 512];  // Probe-Array für Cache-Timing
size_t array1_size = 16;

// VERWUNDBAR: Geheime Daten im benachbarten Speicher
uint8_t secret_data[256] = "SECRET_ENCRYPTION_KEY_12345678";

uint8_t vulnerable_bounds_check(size_t x) {
    // VERWUNDBAR: Spekulative Ausführung ignoriert Grenzenprüfung
    if (x < array1_size) {
        // Während der spekulativen Ausführung kann die CPU dies ausführen,
        // auch wenn x >= array1_size ist

        // VERWUNDBAR: Out-of-Bounds-Lesezugriff in secret_data
        uint8_t value = array1[x];  // x könnte groß sein und Geheimnisse lesen

        // VERWUNDBAR: Abhängiger Ladezugriff bringt Geheimnis in den Cache
        return array2[value * 512];  // Erzeugt Cache-Seitenkanal
    }
    return 0;
}

// Angriff:
// 1. Branch Predictor trainieren, damit (x < array1_size) als wahr erwartet wird
// 2. Aufruf mit x = Offset zu secret_data (z.B. x = secret_data - array1)
// 3. Branch sagt falsch vorher, führt Body spekulativ aus
// 4. array1[x] liest geheimes Byte, obwohl x außerhalb der Grenzen liegt
// 5. array2[secret_byte * 512] wird in den Cache geladen
// 6. Ausführung wird zurückgesetzt, aber Cache-Zustand bleibt erhalten
// 7. Zeitmessung des Zugriffs auf array2 bestimmt, welche Zeile gecacht ist
// 8. Gecachte Zeile verrät den Wert des geheimen Bytes

// VERWUNDBAR: Gemeinsamer Branch-Predictor-Zustand
void vulnerable_indirect_branch(int index) {
    // VERWUNDBAR: Indirekter Branch kann vom Angreifer trainiert werden
    static void (*handlers[4])(void) = {
        handler0, handler1, handler2, handler3
    };

    if (index >= 0 && index < 4) {
        handlers[index]();  // VERWUNDBAR: Spekulativer indirekter Branch
    }

    // Angriff: Angreifer in anderem Thread trainiert Branch Target Buffer
    // Indirekter Branch des Opfers springt spekulativ zu angreifergewählter Adresse
    // Angreifer kann Opfer veranlassen, spekulativ Gadgets auszuführen
}

// VERWUNDBAR: Cache-Timing-Seitenkanal
int vulnerable_cache_timing_leak(const uint8_t* secret_key, const uint8_t* input) {
    uint8_t result = 0;

    for (int i = 0; i < 16; i++) {
        // VERWUNDBAR: Tabellen-Lookup hängt vom geheimen Schlüssel ab
        // Cache-Miss/Hit-Muster verraten Schlüssel-Bits
        result ^= sbox[secret_key[i] ^ input[i]];
    }

    return result;
}
// VERWUNDBAR: Hardware mit gemeinsam genutzten mikroarchitekturellen Ressourcen

module vulnerable_shared_cache (
    input  wire        clk,
    input  wire        rst_n,

    // Core-0-Schnittstelle (vertrauenswürdig)
    input  wire [31:0] core0_addr,
    input  wire        core0_read,
    input  wire [1:0]  core0_security_level,

    // Core-1-Schnittstelle (nicht vertrauenswürdig)
    input  wire [31:0] core1_addr,
    input  wire        core1_read,
    input  wire [1:0]  core1_security_level,

    // Cache-Ausgänge
    output reg  [31:0] cache_data,
    output reg         cache_hit
);

    // VERWUNDBAR: Gemeinsamer Cache zwischen Cores
    // Keine Partitionierung zwischen Sicherheitsstufen
    reg [31:0] cache_data_array [0:255];
    reg [23:0] cache_tag_array [0:255];
    reg        cache_valid [0:255];

    wire [7:0] core0_index = core0_addr[11:4];
    wire [7:0] core1_index = core1_addr[11:4];

    // VERWUNDBAR: Beide Cores teilen sich dieselben Cache-Zeilen
    // Core 1 kann Cache-Zeilen von Core 0 verdrängen
    // Cache-Timing verrät Zugriffsmuster von Core 0 an Core 1

    always @(posedge clk) begin
        // Core-0-Zugriff
        if (core0_read) begin
            // VERWUNDBAR: Zugriff beeinflusst gemeinsamen Cache-Zustand
            // Core 1 kann Timing-Unterschiede beobachten
        end

        // Core-1-Zugriff
        if (core1_read) begin
            // VERWUNDBAR: Kann prüfen, ob Zugriffe von Core 0 erkennbar sind
            // Prime+Probe-Angriff möglich
        end
    end

endmodule

// VERWUNDBAR: Gemeinsamer Branch Predictor
module vulnerable_branch_predictor (
    input  wire        clk,
    input  wire        rst_n,

    // Core-0-Eingänge
    input  wire [31:0] core0_pc,
    input  wire        core0_branch_taken,
    input  wire        core0_update,

    // Core-1-Eingänge
    input  wire [31:0] core1_pc,
    input  wire        core1_branch_taken,
    input  wire        core1_update,

    // Vorhersagen
    output wire        core0_prediction,
    output wire        core1_prediction
);

    // VERWUNDBAR: Gemeinsame Branch-History-Tabelle
    reg [1:0] branch_history_table [0:1023];

    wire [9:0] core0_index = core0_pc[11:2];
    wire [9:0] core1_index = core1_pc[11:2];

    // VERWUNDBAR: Core 1 kann Predictor-Zustand trainieren, der von Core 0 genutzt wird
    // Spectre v2 (Branch Target Injection) Angriff möglich

    always @(posedge clk) begin
        if (core1_update) begin
            // VERWUNDBAR: Branches von Core 1 beeinflussen gemeinsamen Predictor
            // Können Vorhersagen für Core 0 vergiften
            if (core1_branch_taken)
                branch_history_table[core1_index] <= branch_history_table[core1_index] + 1;
            else
                branch_history_table[core1_index] <= branch_history_table[core1_index] - 1;
        end
    end

    // VERWUNDBAR: Core 0 verwendet vergiftete Vorhersagen
    assign core0_prediction = branch_history_table[core0_index][1];
    assign core1_prediction = branch_history_table[core1_index][1];

endmodule

Sichere Lösung

// SICHER: Schutzmassnahmen gegen mikroarchitekturelle Angriffe

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

// SICHER: Spectre-Schutz mit Spekulationsbarriere
#if defined(__x86_64__)
#define speculation_barrier() __asm__ __volatile__("lfence" ::: "memory")
#elif defined(__aarch64__)
#define speculation_barrier() __asm__ __volatile__("csdb" ::: "memory")
#else
#define speculation_barrier() __asm__ __volatile__("" ::: "memory")
#endif

uint8_t array1[16];
uint8_t array2[256 * 512];
size_t array1_size = 16;

uint8_t secure_bounds_check(size_t x) {
    if (x < array1_size) {
        // SICHER: Spekulationsbarriere verhindert spekulative Ausführung
        speculation_barrier();

        uint8_t value = array1[x];
        return array2[value * 512];
    }
    return 0;
}

// SICHER: Index-Maskierung (Grenzen-Clipping)
uint8_t secure_bounds_mask(size_t x) {
    // SICHER: Maske erstellen, die 0 ist wenn x >= array1_size
    size_t mask = ~(x - array1_size) >> (sizeof(size_t) * 8 - 1);
    mask = mask - 1;  // Alle 1en wenn gültig, alle 0en wenn ungültig

    // SICHER: Maske auf Index anwenden - außerhalb der Grenzen wird 0
    x = x & mask;

    uint8_t value = array1[x];
    return array2[value * 512];
}

// SICHER: Retpoline für indirekte Branches (Spectre-v2-Schutz)
#define RETPOLINE_CALL(func, arg) \
    __asm__ __volatile__( \
        "call retpoline_call_target\n" \
        : : "D" (arg), "S" (func) : "memory")

void secure_indirect_branch(int index) {
    static void (*handlers[4])(void) = {
        handler0, handler1, handler2, handler3
    };

    if (index >= 0 && index < 4) {
        // SICHER: Retpoline verwenden, um spekulative indirekte Branch-Angriffe zu verhindern
        // Oder IBRS (Indirect Branch Restricted Speculation) verwenden, falls verfügbar
        speculation_barrier();
        handlers[index]();
    }
}

// SICHER: Konstant-Zeit-Tabellen-Lookup (kein Cache-Timing-Seitenkanal)
uint8_t secure_constant_time_lookup(const uint8_t* table, size_t index, size_t table_size) {
    uint8_t result = 0;

    // SICHER: ALLE Tabelleneinträge zugreifen, den richtigen per Maskierung auswählen
    for (size_t i = 0; i < table_size; i++) {
        // Konstant-Zeit-Vergleich: Maske ist alle 1en wenn i == index, sonst alle 0en
        uint8_t mask = -((i ^ index) == 0);
        result |= table[i] & mask;
    }

    return result;
}

// SICHER: Cache-Timing-resistentes AES (Bit-Sliced-Implementierung)
void secure_aes_no_cache_timing(const uint8_t* key, const uint8_t* input,
                                 uint8_t* output) {
    // SICHER: Bit-Sliced-Implementierung ohne Tabellen-Lookups verwenden
    // Alle Operationen sind arithmetisch/logisch, keine speicherabhängigen Zugriffsmuster
    aes_bitsliced_encrypt(key, input, output);
}
// SICHER: Hardware mit partitionierten mikroarchitekturellen Ressourcen

module secure_partitioned_cache (
    input  wire        clk,
    input  wire        rst_n,

    // Core-0-Schnittstelle (vertrauenswürdig)
    input  wire [31:0] core0_addr,
    input  wire        core0_read,
    input  wire [1:0]  core0_security_level,

    // Core-1-Schnittstelle (nicht vertrauenswürdig)
    input  wire [31:0] core1_addr,
    input  wire        core1_read,
    input  wire [1:0]  core1_security_level,

    // Cache-Ausgänge
    output reg  [31:0] core0_data,
    output reg         core0_hit,
    output reg  [31:0] core1_data,
    output reg         core1_hit
);

    // SICHER: Separate Cache-Partitionen pro Sicherheitsdomäne
    // Way-Partitionierung: Core 0 nutzt Ways 0-1, Core 1 nutzt Ways 2-3

    reg [31:0] cache_data_way0 [0:127];  // Core-0-Partition
    reg [31:0] cache_data_way1 [0:127];
    reg [31:0] cache_data_way2 [0:127];  // Core-1-Partition
    reg [31:0] cache_data_way3 [0:127];

    reg [23:0] cache_tag_way0 [0:127];
    reg [23:0] cache_tag_way1 [0:127];
    reg [23:0] cache_tag_way2 [0:127];
    reg [23:0] cache_tag_way3 [0:127];

    // SICHER: Jeder Core kann nur seine eigene Partition beeinflussen
    // Keine domainübergreifende Cache-Interferenz

    wire [6:0] core0_index = core0_addr[10:4];
    wire [6:0] core1_index = core1_addr[10:4];

    always @(posedge clk) begin
        // SICHER: Core 0 greift nur auf Ways 0-1 zu
        if (core0_read) begin
            // Cache-Lookup in partitionierten Ways
            // Kann Zeilen von Core 1 nicht verdrängen
        end

        // SICHER: Core 1 greift nur auf Ways 2-3 zu
        if (core1_read) begin
            // Kann Zugriffsmuster von Core 0 nicht beobachten
        end
    end

endmodule

// SICHER: Kontextbezogener Branch Predictor
module secure_branch_predictor (
    input  wire        clk,
    input  wire        rst_n,

    // Core-0-Eingänge
    input  wire [31:0] core0_pc,
    input  wire        core0_branch_taken,
    input  wire        core0_update,
    input  wire [7:0]  core0_context_id,

    // Core-1-Eingänge
    input  wire [31:0] core1_pc,
    input  wire        core1_branch_taken,
    input  wire        core1_update,
    input  wire [7:0]  core1_context_id,

    // Vorhersagen
    output wire        core0_prediction,
    output wire        core1_prediction
);

    // SICHER: Separater Branch-Predictor-Zustand pro Kontext
    reg [1:0] bht_context0 [0:1023];  // Core 0 / vertrauenswürdig
    reg [1:0] bht_context1 [0:1023];  // Core 1 / nicht vertrauenswürdig

    wire [9:0] core0_index = core0_pc[11:2];
    wire [9:0] core1_index = core1_pc[11:2];

    // SICHER: Aktualisierungen betreffen nur den eigenen Kontext-Predictor
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            // Predictoren initialisieren
        end else begin
            // SICHER: Core 0 aktualisiert nur seinen Predictor
            if (core0_update) begin
                if (core0_branch_taken)
                    bht_context0[core0_index] <= bht_context0[core0_index] + 1;
                else
                    bht_context0[core0_index] <= bht_context0[core0_index] - 1;
            end

            // SICHER: Core 1 aktualisiert nur seinen Predictor
            if (core1_update) begin
                if (core1_branch_taken)
                    bht_context1[core1_index] <= bht_context1[core1_index] + 1;
                else
                    bht_context1[core1_index] <= bht_context1[core1_index] - 1;
            end
        end
    end

    // SICHER: Jeder Core verwendet nur seinen eigenen Vorhersage-Zustand
    assign core0_prediction = bht_context0[core0_index][1];
    assign core1_prediction = bht_context1[core1_index][1];

    // SICHER: Kontextwechsel löscht/wechselt Predictor-Zustand
    // SICHER: IBPB (Indirect Branch Prediction Barrier) Unterstützung für Flushes

endmodule

CVE-Beispiele

  • CVE-2017-5753: Spectre Variante 1 - Bounds Check Bypass durch spekulative Ausführung.
  • CVE-2017-5715: Spectre Variante 2 - Branch Target Injection.
  • CVE-2017-5754: Meltdown - Rogue Data Cache Load.
  • CVE-2018-3639: Speculative Store Bypass.
  • CVE-2019-1125: SWAPGS-Angriff.

Verwandte CWEs

  • CWE-203: Observable Discrepancy (Eltern)
  • CWE-1189: Improper Isolation of Shared Resources on System-on-a-Chip (Eltern)
  • CWE-1198: Privilege Separation and Access Control Issues (Kategorie)
  • CWE-1300: Improper Protection of Physical Side Channels (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1303: Non-Transparent Sharing of Microarchitectural Resources." https://cwe.mitre.org/data/definitions/1303.html
  2. Kocher, P., et al. "Spectre Attacks: Exploiting Speculative Execution"
  3. Lipp, M., et al. "Meltdown: Reading Kernel Memory from User Space"