Gespiegelte Regionen mit unterschiedlichen Werten

Beschreibung

Gespiegelte Regionen mit unterschiedlichen Werten tritt auf, wenn die Architektur eines Produkts Regionen (wie Cache-Speicher oder Schattenkopien von Registern) dupliziert, ohne sicherzustellen, dass deren Inhalte stets synchron bleiben. Leistungsoptimierung erfordert oft die Duplizierung von Ressourcen, aber das Produkt muss sicherstellen, dass die lokale Kopie immer die Originalkopie wahrheitsgetreu widerspiegelt. Wenn die Synchronisation fehlschlägt, werden Berechnungsergebnisse unzuverlässig und Sicherheitsmechanismen können umgangen werden.

Risiko

Desynchronisierte gespiegelte Regionen haben schwerwiegende Sicherheitsauswirkungen. Zugriffskontrolle kann über veraltete Berechtigungen umgangen werden. Cache-Vergiftung kann zu Privilege Escalation führen. Schattenregister können fehlerhaften Sicherheitszustand enthalten. Speicherschutz kann umgangen werden. Kryptografische Schlüssel können inkonsistent sein. Der Authentifizierungszustand kann über Komponenten hinweg abweichen. Angreifer können Race Conditions während der Synchronisation ausnutzen. Sicherheitsentscheidungen können auf veralteten Daten basieren.

Lösung

Minimieren Sie Zeiträume der Desynchronisation und machen Sie den Aktualisierungsprozess so robust wie möglich. Stellen Sie sicher, dass Originalkopien Aktualisierungsbenachrichtigungen senden. Überprüfen Sie, dass Schattenkopien empfangene Aktualisierungen ausführen. Schützen Sie vor Race Conditions während Aktualisierungsfenstern. Authentifizieren Sie Aktualisierungsanfragen zur Verhinderung von Spoofing. Implementieren Sie Abschlussbestätigung zur Verhinderung von Rücksetzung. Fügen Sie Integritätsüberprüfung zwischen Original und Spiegel hinzu. Implementieren Sie atomare Aktualisierungsmechanismen.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitUmfang: Vertraulichkeit

Unbefugter Datenzugriff - Veraltete gespiegelte Daten können Zugriff auf geschützte Ressourcen ermöglichen.
IntegritätUmfang: Integrität

Datenbeschädigung - Inkonsistente Spiegel können fehlerhafte Berechnungen oder Sicherheitsentscheidungen verursachen.
ZugriffskontrolleUmfang: Zugriffskontrolle

Schutzmechanismus umgehen - Veraltete Berechtigungsdaten in Spiegeln können unbefugten Zugriff ermöglichen.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Register-Schattenkopie ohne Synchronisation

module vulnerable_register_mirror (
    input wire clk,
    input wire reset_n,
    input wire [31:0] master_write_data,
    input wire master_write_enable,
    input wire [7:0] master_addr,
    input wire shadow_read_enable,
    input wire [7:0] shadow_read_addr,
    output reg [31:0] shadow_read_data
);

    // Master-Register
    reg [31:0] master_regs [0:255];

    // Schattenkopie für schnellen Zugriff
    reg [31:0] shadow_regs [0:255];

    // VERWUNDBAR: Master-Aktualisierung ohne Schatten-Synchronisation
    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            integer i;
            for (i = 0; i < 256; i = i + 1) begin
                master_regs[i] <= 32'h0;
                shadow_regs[i] <= 32'h0;
            end
        end
        else if (master_write_enable) begin
            // Master aktualisieren
            master_regs[master_addr] <= master_write_data;

            // VERWUNDBAR: Schatten-Aktualisierung ist asynchron
            // Race Condition: Schatten wird möglicherweise nicht vor dem nächsten Lesen aktualisiert
        end
    end

    // VERWUNDBAR: Separater Prozess für Schatten-Aktualisierung
    // Kann Aktualisierungen verpassen oder desynchronisiert werden
    always @(posedge clk) begin
        // Hintergrund-Synchronisation - kann hinterherhinken
        shadow_regs[0] <= master_regs[0];
        shadow_regs[1] <= master_regs[1];
        // ... synchronisiert nur wenige Register pro Zyklus
    end

    // Lesen aus möglicherweise veraltetem Schatten
    always @(*) begin
        if (shadow_read_enable) begin
            shadow_read_data = shadow_regs[shadow_read_addr];
        end
    end

endmodule

// VERWUNDBAR: Cache ohne Kohärenz
module vulnerable_cache_mirror (
    input wire clk,
    input wire reset_n,
    input wire [31:0] mem_addr,
    input wire [31:0] mem_write_data,
    input wire mem_write_enable,
    input wire mem_read_enable,
    output reg [31:0] mem_read_data,
    output reg cache_hit
);

    // Hauptspeicher
    reg [31:0] main_memory [0:1023];

    // Cache (Spiegel häufig zugegriffener Daten)
    reg [31:0] cache_data [0:63];
    reg [25:0] cache_tags [0:63];
    reg cache_valid [0:63];

    wire [5:0] cache_index = mem_addr[7:2];
    wire [25:0] cache_tag = mem_addr[31:6];

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            // Initialisieren
            integer i;
            for (i = 0; i < 64; i = i + 1) begin
                cache_valid[i] <= 1'b0;
            end
        end
        else if (mem_write_enable) begin
            // VERWUNDBAR: Write-Through ohne Invalidierungsbenachrichtigung
            main_memory[mem_addr[11:2]] <= mem_write_data;

            // Cache aktualisieren falls vorhanden
            if (cache_valid[cache_index] && cache_tags[cache_index] == cache_tag) begin
                cache_data[cache_index] <= mem_write_data;
            end

            // VERWUNDBAR: Andere Caches (bei Multiprozessor) nicht benachrichtigt
            // Sie werden veraltete Daten haben
        end
    end

    // Lesen mit möglicherweise veraltetem Cache
    always @(*) begin
        if (mem_read_enable) begin
            if (cache_valid[cache_index] && cache_tags[cache_index] == cache_tag) begin
                // VERWUNDBAR: Cache-Treffer aber Daten können veraltet sein
                mem_read_data = cache_data[cache_index];
                cache_hit = 1'b1;
            end
            else begin
                mem_read_data = main_memory[mem_addr[11:2]];
                cache_hit = 1'b0;
            end
        end
    end

endmodule
// VERWUNDBAR: Speicherabgebildeter Register-Spiegel ohne Synchronisation

typedef struct {
    volatile uint32_t* master_base;
    uint32_t* shadow_copy;
    size_t num_registers;
} VulnerableRegisterMirror;

void vulnerable_init(VulnerableRegisterMirror* mirror) {
    // VERWUNDBAR: Einmalige Kopie bei Initialisierung
    for (size_t i = 0; i < mirror->num_registers; i++) {
        mirror->shadow_copy[i] = mirror->master_base[i];
    }
    // Schattenkopie wird nach jeder Master-Aktualisierung veraltet sein
}

uint32_t vulnerable_read(VulnerableRegisterMirror* mirror, size_t index) {
    // VERWUNDBAR: Lesen aus Schatten ohne Aktualitätsprüfung
    return mirror->shadow_copy[index];
}

void vulnerable_write(VulnerableRegisterMirror* mirror, size_t index, uint32_t value) {
    // Zum Master schreiben
    mirror->master_base[index] = value;

    // VERWUNDBAR: Schatten-Aktualisierung kann unterbrochen werden
    // Ein anderer Thread könnte veralteten Schatten zwischen diesen Zeilen lesen
    mirror->shadow_copy[index] = value;
}

// VERWUNDBAR: DMA-Puffer-Spiegelung
typedef struct {
    uint8_t* cpu_buffer;
    uint8_t* dma_buffer;
    size_t size;
} VulnerableDMAMirror;

void vulnerable_dma_prepare(VulnerableDMAMirror* mirror) {
    // VERWUNDBAR: Kopie in DMA-Puffer ohne Synchronisation
    memcpy(mirror->dma_buffer, mirror->cpu_buffer, mirror->size);

    // DMA-Engine sieht möglicherweise teilweise aktualisierten Puffer
    // Keine Speicherbarriere oder Cache-Flush
}

void vulnerable_dma_complete(VulnerableDMAMirror* mirror) {
    // VERWUNDBAR: Rückkopie ohne Synchronisation
    memcpy(mirror->cpu_buffer, mirror->dma_buffer, mirror->size);

    // CPU-Cache hat möglicherweise noch alte Daten
    // Keine Cache-Invalidierung
}

Sichere Lösung

// SICHER: Register-Schatten mit verifizierter Synchronisation

module secure_register_mirror (
    input wire clk,
    input wire reset_n,
    input wire [31:0] master_write_data,
    input wire master_write_enable,
    input wire [7:0] master_addr,
    input wire shadow_read_enable,
    input wire [7:0] shadow_read_addr,
    output reg [31:0] shadow_read_data,
    output reg sync_valid,
    output reg sync_error
);

    // Master-Register
    reg [31:0] master_regs [0:255];

    // Schattenkopie mit Versionsverfolgung
    reg [31:0] shadow_regs [0:255];
    reg [15:0] master_version [0:255];
    reg [15:0] shadow_version [0:255];

    // SICHER: Atomare Aktualisierung von Master und Schatten
    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            integer i;
            for (i = 0; i < 256; i = i + 1) begin
                master_regs[i] <= 32'h0;
                shadow_regs[i] <= 32'h0;
                master_version[i] <= 16'h0;
                shadow_version[i] <= 16'h0;
            end
            sync_valid <= 1'b1;
            sync_error <= 1'b0;
        end
        else if (master_write_enable) begin
            // SICHER: Master und Schatten atomar aktualisieren
            master_regs[master_addr] <= master_write_data;
            shadow_regs[master_addr] <= master_write_data;

            // Versionen gemeinsam inkrementieren
            master_version[master_addr] <= master_version[master_addr] + 1;
            shadow_version[master_addr] <= master_version[master_addr] + 1;
        end
    end

    // SICHER: Synchronisation vor Datenrückgabe überprüfen
    always @(*) begin
        if (shadow_read_enable) begin
            // Versionsübereinstimmung prüfen
            if (shadow_version[shadow_read_addr] == master_version[shadow_read_addr]) begin
                shadow_read_data = shadow_regs[shadow_read_addr];
                sync_valid = 1'b1;
            end
            else begin
                // SICHER: Ausfallsicher bei Nichtübereinstimmung
                shadow_read_data = 32'h0;
                sync_valid = 1'b0;
                sync_error = 1'b1;
            end
        end
    end

    // SICHER: Periodische Integritätsprüfung
    reg [7:0] check_index;
    always @(posedge clk) begin
        if (master_regs[check_index] != shadow_regs[check_index]) begin
            sync_error <= 1'b1;
        end
        check_index <= check_index + 1;
    end

endmodule

// SICHER: Cache mit Kohärenzprotokoll
module secure_cache_coherent (
    input wire clk,
    input wire reset_n,
    input wire [31:0] mem_addr,
    input wire [31:0] mem_write_data,
    input wire mem_write_enable,
    input wire mem_read_enable,
    input wire [3:0] core_id,
    input wire invalidate_req,
    input wire [31:0] invalidate_addr,
    output reg [31:0] mem_read_data,
    output reg cache_hit,
    output reg invalidate_ack
);

    // Cache-Zustand (MESI-Protokoll)
    parameter INVALID = 2'b00;
    parameter SHARED = 2'b01;
    parameter EXCLUSIVE = 2'b10;
    parameter MODIFIED = 2'b11;

    reg [31:0] cache_data [0:63];
    reg [25:0] cache_tags [0:63];
    reg [1:0] cache_state [0:63];

    wire [5:0] cache_index = mem_addr[7:2];
    wire [25:0] cache_tag = mem_addr[31:6];

    wire [5:0] inv_index = invalidate_addr[7:2];
    wire [25:0] inv_tag = invalidate_addr[31:6];

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            integer i;
            for (i = 0; i < 64; i = i + 1) begin
                cache_state[i] <= INVALID;
            end
            invalidate_ack <= 1'b0;
        end
        // SICHER: Invalidierungsanfragen von anderen Caches behandeln
        else if (invalidate_req) begin
            if (cache_tags[inv_index] == inv_tag &&
                cache_state[inv_index] != INVALID) begin
                // Zurückschreiben falls modifiziert
                if (cache_state[inv_index] == MODIFIED) begin
                    // Zurückschreiben in Speicher auslösen vor Invalidierung
                    writeback_to_memory(inv_index);
                end
                cache_state[inv_index] <= INVALID;
            end
            invalidate_ack <= 1'b1;
        end
        else if (mem_write_enable) begin
            // SICHER: Aktualisierung mit korrektem Zustandsübergang
            cache_data[cache_index] <= mem_write_data;
            cache_tags[cache_index] <= cache_tag;
            cache_state[cache_index] <= MODIFIED;

            // SICHER: Invalidierung an andere Caches senden
            // (wird durch Verbindungsstruktur behandelt)
        end
        else begin
            invalidate_ack <= 1'b0;
        end
    end

    always @(*) begin
        if (mem_read_enable) begin
            if (cache_tags[cache_index] == cache_tag &&
                cache_state[cache_index] != INVALID) begin
                mem_read_data = cache_data[cache_index];
                cache_hit = 1'b1;
            end
            else begin
                // Cache-Fehlschlag - aus Speicher holen
                mem_read_data = fetch_from_memory(mem_addr);
                cache_hit = 1'b0;
            end
        end
    end

endmodule
// SICHER: Speicherabgebildeter Register-Spiegel mit Synchronisation

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

typedef struct {
    volatile uint32_t* master_base;
    uint32_t* shadow_copy;
    atomic_uint* versions;
    size_t num_registers;
    pthread_spinlock_t lock;
} SecureRegisterMirror;

int secure_init(SecureRegisterMirror* mirror) {
    pthread_spin_init(&mirror->lock, PTHREAD_PROCESS_PRIVATE);

    // SICHER: Atomare Initialisierung mit Versionsverfolgung
    pthread_spin_lock(&mirror->lock);
    for (size_t i = 0; i < mirror->num_registers; i++) {
        mirror->shadow_copy[i] = mirror->master_base[i];
        atomic_store(&mirror->versions[i], 1);
    }
    pthread_spin_unlock(&mirror->lock);

    return 0;
}

uint32_t secure_read(SecureRegisterMirror* mirror, size_t index) {
    uint32_t value;
    unsigned int version1, version2;

    // SICHER: Seqlock-Muster für konsistentes Lesen
    do {
        version1 = atomic_load(&mirror->versions[index]);

        // Schatten lesen
        value = mirror->shadow_copy[index];

        // Speicherbarriere
        atomic_thread_fence(memory_order_acquire);

        version2 = atomic_load(&mirror->versions[index]);

        // Wiederholen falls Version sich während des Lesens geändert hat
    } while (version1 != version2 || (version1 & 1));

    // SICHER: Periodisch gegen Master verifizieren
    if ((version1 % 100) == 0) {
        uint32_t master_value = mirror->master_base[index];
        if (value != master_value) {
            // Resynchronisieren und Fehler protokollieren
            secure_resync(mirror, index);
            return mirror->shadow_copy[index];
        }
    }

    return value;
}

void secure_write(SecureRegisterMirror* mirror, size_t index, uint32_t value) {
    pthread_spin_lock(&mirror->lock);

    // SICHER: Version als ungerade markieren (Schreiben im Gange)
    unsigned int old_version = atomic_fetch_add(&mirror->versions[index], 1);

    // Speicherbarriere
    atomic_thread_fence(memory_order_release);

    // Atomar in beide schreiben
    mirror->master_base[index] = value;
    mirror->shadow_copy[index] = value;

    // Speicherbarriere
    atomic_thread_fence(memory_order_release);

    // Version als gerade markieren (Schreiben abgeschlossen)
    atomic_fetch_add(&mirror->versions[index], 1);

    pthread_spin_unlock(&mirror->lock);
}

// SICHER: DMA-Puffer-Spiegelung mit korrekter Synchronisation
typedef struct {
    uint8_t* cpu_buffer;
    uint8_t* dma_buffer;
    size_t size;
    volatile int sync_state;
} SecureDMAMirror;

void secure_dma_prepare(SecureDMAMirror* mirror) {
    // SICHER: CPU-Cache zuerst leeren
    cache_flush(mirror->cpu_buffer, mirror->size);

    // Speicherbarriere
    __sync_synchronize();

    // In DMA-Puffer kopieren
    memcpy(mirror->dma_buffer, mirror->cpu_buffer, mirror->size);

    // SICHER: Speicherbarriere vor DMA-Start
    __sync_synchronize();

    mirror->sync_state = 1;  // DMA bereit
}

void secure_dma_complete(SecureDMAMirror* mirror) {
    // SICHER: Auf DMA-Abschluss warten
    while (!dma_is_complete()) {
        // Aktives Warten oder Yielden
    }

    // SICHER: CPU-Cache invalidieren
    cache_invalidate(mirror->cpu_buffer, mirror->size);

    // Speicherbarriere
    __sync_synchronize();

    // Zurückkopieren
    memcpy(mirror->cpu_buffer, mirror->dma_buffer, mirror->size);

    // Speicherbarriere nach Aktualisierung
    __sync_synchronize();

    mirror->sync_state = 2;  // CPU bereit
}

CVE-Beispiele

Schwachstellen in gespiegelten Regionen wurden in Prozessor-Cache-Implementierungen, TLB-Einträgen und Schatten-Registerbänken gefunden, bei denen Angreifer Synchronisationslücken ausnutzten, um auf geschützte Daten zuzugreifen oder Zugriffskontrollen zu umgehen.


Verwandte CWEs

  • CWE-1250: Unsachgemäße Erhaltung der Konsistenz zwischen unabhängigen Darstellungen gemeinsamen Zustands (übergeordnet)
  • CWE-1312: Fehlender Schutz für gespiegelte Regionen in On-Chip-Fabric-Firewall (verwandt)
  • CWE-362: Gleichzeitige Ausführung mit gemeinsamer Ressource und unsachgemäßer Synchronisation (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1251: Mirrored Regions with Different Values." https://cwe.mitre.org/data/definitions/1251.html
  2. Patterson & Hennessy. "Computer Organization and Design" - Cache Coherence Protocols
  3. MESI Protocol Documentation