Unsachgemäße Schreibbehandlung in nichtflüchtigen Speichern mit begrenzter Schreibkapazität

Beschreibung

Unsachgemäße Schreibbehandlung in nichtflüchtigen Speichern mit begrenzter Schreibkapazität tritt auf, wenn ein Produkt Wear-Leveling-Operationen in nichtflüchtigen Speichern mit begrenzten Programmier-/Löschzyklen nicht oder fehlerhaft implementiert. Nichtflüchtige Speichertechnologien wie NAND Flash und EEPROM haben individuell löschbare Segmente mit begrenzten Programmier-/Löschzyklen. Wear Leveling bildet logische Blockschreibvorgänge auf verschiedene physische Blöcke ab, um vorzeitiges Versagen durch konzentrierte Schreibvorgänge zu verhindern. Eine unsachgemäße Implementierung dieser Technik schafft Schwachstellen, die Angreifer ausnutzen können, um Denial of Service durch beschleunigte Speicherdegradation zu verursachen.

Risiko

Unsachgemäßes Wear Leveling hat erhebliche Verfügbarkeitsauswirkungen. Speicher kann vorzeitig unzuverlässig werden. Daten können aufgrund abgenutzter Blöcke verloren gehen. Denial of Service kann durch Schreibangriffe erreicht werden. Die Systemlebensdauer kann erheblich verkürzt werden. Kritischer Firmware-Speicher kann ausfallen. Sicherheitskritische Protokolle können verloren gehen. Die Boot-Fähigkeit kann beeinträchtigt werden. Wiederherstellungsoptionen können eliminiert werden.

Lösung

Integrieren Sie sichere Wear-Leveling-Algorithmen und stellen Sie sicher, dass sie nicht umgangen werden können. Implementieren Sie Schreibratenbegrenzung zur Vermeidung beschleunigten Verschleißes. Überwachen Sie Block-Verschleißgrade und alarmieren Sie bei abnormalen Mustern. Reservieren Sie Ersatzblöcke für Wear Leveling. Implementieren Sie Bad-Block-Management. Testen Sie die Wirksamkeit des Wear Leveling. Dokumentieren Sie die erwartete Schreibdauerhaftigkeit. Erwägen Sie Speichertechnologien mit höherer Dauerhaftigkeit für kritische Daten.

Häufige Auswirkungen

AuswirkungDetails
VerfügbarkeitUmfang: Verfügbarkeit

DoS: Instabilität - Angreifer können programmatisch dafür sorgen, dass Speicher durch unsachgemäße Wear-Leveling-Implementierung oder Umgehung des Wear Leveling deutlich schneller als erwartet unzuverlässig wird.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: Keine Wear-Leveling-Implementierung

#include <stdint.h>

#define FLASH_BLOCK_COUNT 1024
#define FLASH_BLOCK_SIZE 4096
#define MAX_ERASE_CYCLES 100000

// Direkte Zuordnung - kein Wear Leveling
uint32_t logical_to_physical[FLASH_BLOCK_COUNT];

void vulnerable_init(void) {
    // VERWUNDBAR: Direkte 1:1-Zuordnung
    for (int i = 0; i < FLASH_BLOCK_COUNT; i++) {
        logical_to_physical[i] = i;
    }
}

void vulnerable_write_block(uint32_t logical_block, uint8_t* data) {
    // VERWUNDBAR: Schreibt immer auf denselben physischen Block
    uint32_t physical_block = logical_to_physical[logical_block];

    // Kein Wear Leveling - dieser Block wird zuerst verschleißen
    flash_erase_block(physical_block);
    flash_write_block(physical_block, data);
}

// Angriff: Wiederholt auf denselben logischen Block schreiben
void wear_attack(void) {
    uint8_t data[FLASH_BLOCK_SIZE];

    // ANGRIFF: Auf denselben Block schreiben, bis er ausfällt
    while (1) {
        for (int i = 0; i < MAX_ERASE_CYCLES + 1; i++) {
            // Block 0 wird nach ~100.000 Schreibvorgängen ausfallen
            vulnerable_write_block(0, data);
        }
        // Systemspeicher jetzt unzuverlässig!
    }
}

// VERWUNDBAR: Einfacher Zähler ohne Wear Leveling
class VulnerableCounter {
private:
    uint32_t counter_address;

public:
    void increment(void) {
        uint32_t value = flash_read(counter_address);
        // VERWUNDBAR: Schreibt immer an dieselbe Stelle
        flash_erase(counter_address);
        flash_write(counter_address, value + 1);
        // Zählerposition wird schnell verschleißen
    }
};
// VERWUNDBAR: Flash-Controller ohne Wear Leveling

module vulnerable_flash_controller (
    input wire clk,
    input wire reset_n,
    input wire [31:0] logical_address,
    input wire [31:0] write_data,
    input wire write_enable,
    input wire read_enable,
    output reg [31:0] read_data,
    output reg operation_complete
);

    // VERWUNDBAR: Direkte Adresszuordnung
    // Keine Wear-Leveling-Logik

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            operation_complete <= 1'b0;
        end
        else if (write_enable) begin
            // VERWUNDBAR: Direkt an physische Adresse schreiben
            // Gleiche logische Adresse = gleiche physische Adresse = beschleunigter Verschleiß
            flash_write(logical_address, write_data);
            operation_complete <= 1'b1;
        end
        else if (read_enable) begin
            read_data <= flash_read(logical_address);
            operation_complete <= 1'b1;
        end
    end

endmodule

Sichere Lösung

// SICHER: Wear-Leveling-Implementierung

#include <stdint.h>
#include <algorithm>

#define FLASH_BLOCK_COUNT 1024
#define FLASH_BLOCK_SIZE 4096
#define MAX_ERASE_CYCLES 100000
#define WEAR_THRESHOLD 1000  // Leveling auslösen, wenn Differenz diesen Wert überschreitet

class SecureFlashManager {
private:
    struct BlockInfo {
        uint32_t logical_block;
        uint32_t erase_count;
        bool valid;
        bool bad;
    };

    BlockInfo block_table[FLASH_BLOCK_COUNT];
    uint32_t logical_to_physical[FLASH_BLOCK_COUNT];
    uint32_t spare_blocks[64];  // Reserveblöcke für Wear Leveling
    int spare_count;

public:
    void init(void) {
        // Verschleißüberwachung initialisieren
        for (int i = 0; i < FLASH_BLOCK_COUNT - 64; i++) {
            block_table[i].logical_block = i;
            block_table[i].erase_count = read_erase_count(i);
            block_table[i].valid = true;
            block_table[i].bad = is_bad_block(i);
            logical_to_physical[i] = i;
        }

        // Reservepool initialisieren
        spare_count = 0;
        for (int i = FLASH_BLOCK_COUNT - 64; i < FLASH_BLOCK_COUNT; i++) {
            if (!is_bad_block(i)) {
                spare_blocks[spare_count++] = i;
            }
        }
    }

    bool write_block(uint32_t logical_block, uint8_t* data) {
        if (logical_block >= FLASH_BLOCK_COUNT - 64) {
            return false;  // Ungültiger Block
        }

        uint32_t physical_block = logical_to_physical[logical_block];

        // Prüfen, ob Wear Leveling durchgeführt werden sollte
        if (should_level_wear(physical_block)) {
            physical_block = perform_wear_leveling(logical_block);
            if (physical_block == INVALID_BLOCK) {
                return false;  // Keine Reserveblöcke verfügbar
            }
        }

        // Löschen und schreiben
        if (!flash_erase_block(physical_block)) {
            mark_bad_block(physical_block);
            return write_block(logical_block, data);  // Mit neuem Block wiederholen
        }

        block_table[physical_block].erase_count++;
        save_erase_count(physical_block);

        return flash_write_block(physical_block, data);
    }

private:
    bool should_level_wear(uint32_t physical_block) {
        uint32_t min_wear = get_min_erase_count();
        uint32_t block_wear = block_table[physical_block].erase_count;

        // Leveling, wenn dieser Block deutlich stärker abgenutzt ist
        return (block_wear - min_wear) > WEAR_THRESHOLD;
    }

    uint32_t perform_wear_leveling(uint32_t logical_block) {
        // Am wenigsten abgenutzten Reserveblock finden
        uint32_t new_physical = find_least_worn_spare();
        if (new_physical == INVALID_BLOCK) {
            // Keine Reserve - am wenigsten abgenutzten verwendeten Block finden
            new_physical = find_least_worn_block();
            if (new_physical == INVALID_BLOCK) {
                return INVALID_BLOCK;
            }
            // Daten von diesem Block an alten Ort verschieben
            swap_blocks(new_physical, logical_to_physical[logical_block]);
        }

        // Zuordnung aktualisieren
        uint32_t old_physical = logical_to_physical[logical_block];
        logical_to_physical[logical_block] = new_physical;

        // Alter Block wird Reserve
        add_to_spare_pool(old_physical);

        return new_physical;
    }

    uint32_t get_min_erase_count(void) {
        uint32_t min = UINT32_MAX;
        for (int i = 0; i < FLASH_BLOCK_COUNT; i++) {
            if (!block_table[i].bad && block_table[i].erase_count < min) {
                min = block_table[i].erase_count;
            }
        }
        return min;
    }
};

// SICHER: Sicherer Zähler mit Verschleißverteilung
class SecureCounter {
private:
    SecureFlashManager* flash_mgr;
    uint32_t counter_blocks[16];  // Mehrere Blöcke für Zähler
    int current_block_index;

public:
    uint64_t read_counter(void) {
        // Zähler über mehrere Blöcke gespeichert
        uint64_t value = 0;
        for (int i = 0; i < 16; i++) {
            value += flash_mgr->read_block(counter_blocks[i]);
        }
        return value;
    }

    void increment(void) {
        // SICHER: Über mehrere Blöcke rotieren
        current_block_index = (current_block_index + 1) % 16;

        uint8_t data[FLASH_BLOCK_SIZE] = {0};
        data[0] = 1;  // Inkrementierungsmarkierung

        flash_mgr->write_block(counter_blocks[current_block_index], data);
    }
};
// SICHER: Flash-Controller mit Wear Leveling

module secure_flash_controller (
    input wire clk,
    input wire reset_n,
    input wire [31:0] logical_address,
    input wire [31:0] write_data,
    input wire write_enable,
    input wire read_enable,
    output reg [31:0] read_data,
    output reg operation_complete,
    output reg wear_warning
);

    // Wear-Leveling-Tabelle
    reg [31:0] logical_to_physical [0:1023];
    reg [23:0] erase_count [0:1023];

    // Schwellenwert für Wear Leveling
    parameter WEAR_THRESHOLD = 24'd1000;

    // Wear-Leveling-Zustandsautomat
    reg [2:0] wl_state;
    parameter WL_IDLE = 3'h0;
    parameter WL_CHECK = 3'h1;
    parameter WL_SWAP = 3'h2;
    parameter WL_UPDATE = 3'h3;

    wire [9:0] logical_block = logical_address[31:22];
    wire [9:0] physical_block;

    // Minimalen und aktuellen Löschzähler finden
    reg [23:0] min_erase_count;
    reg [23:0] current_erase_count;
    reg need_leveling;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            wl_state <= WL_IDLE;
            operation_complete <= 1'b0;
            wear_warning <= 1'b0;
        end
        else begin
            case (wl_state)
                WL_IDLE: begin
                    if (write_enable) begin
                        wl_state <= WL_CHECK;
                        current_erase_count <= erase_count[logical_to_physical[logical_block]];
                    end
                end

                WL_CHECK: begin
                    // SICHER: Prüfen, ob Wear Leveling erforderlich
                    if ((current_erase_count - min_erase_count) > WEAR_THRESHOLD) begin
                        need_leveling <= 1'b1;
                        wl_state <= WL_SWAP;
                    end else begin
                        wl_state <= WL_UPDATE;
                    end
                end

                WL_SWAP: begin
                    // Blocktausch für Wear Leveling durchführen
                    perform_block_swap();
                    wl_state <= WL_UPDATE;
                end

                WL_UPDATE: begin
                    // Schreibvorgang durchführen und Löschzähler aktualisieren
                    flash_write(logical_to_physical[logical_block], write_data);
                    erase_count[logical_to_physical[logical_block]] <=
                        erase_count[logical_to_physical[logical_block]] + 1;

                    operation_complete <= 1'b1;
                    wl_state <= WL_IDLE;

                    // Warnung bei Annäherung an Lebensdauerende
                    if (current_erase_count > 24'd90000) begin
                        wear_warning <= 1'b1;
                    end
                end
            endcase
        end
    end

    // Ratenbegrenzung zur Verhinderung beschleunigter Verschleißangriffe
    reg [31:0] write_counter;
    reg [31:0] last_write_time;
    parameter WRITE_RATE_LIMIT = 32'd1000;  // Maximale Schreibvorgänge pro Sekunde

    always @(posedge clk) begin
        if (write_enable) begin
            if ((current_time - last_write_time) < WRITE_RATE_LIMIT) begin
                // SICHER: Schreibvorgänge ratenbegrenzen, um Verschleißangriffe zu verhindern
                operation_complete <= 1'b0;  // Schreibvorgang ablehnen
            end else begin
                last_write_time <= current_time;
            end
        end
    end

endmodule

CVE-Beispiele

Wear-Leveling-Schwachstellen wurden in verschiedenen Speichersystemen gefunden, bei denen Angreifer die Speicherdegradation durch konzentrierte Schreibmuster beschleunigen könnten.


Verwandte CWEs

  • CWE-400: Unkontrollierter Ressourcenverbrauch (übergeordnet)
  • CWE-1202: Speicher- und Speicherprobleme (Kategoriemitglied)

Referenzen

  1. MITRE Corporation. "CWE-1246: Improper Write Handling in Limited-write Non-Volatile Memories." https://cwe.mitre.org/data/definitions/1246.html
  2. Qureshi et al.: "Enhancing Lifetime and Security of PCM-Based Main Memory with Start-Gap Wear Leveling"
  3. Micron: "Bad Block Management in NAND Flash Memory"