Remanent Data Readable after Memory Erase

Description

Remanent Data Readable after Memory Erase occurs when confidential information stored in memory circuits is readable or recoverable after being cleared or erased. Data remanence occurs when memory contents persist after erase operations due to performance optimization designs that erase metadata but not actual content, physical properties of memory circuits (SRAM/DRAM charge retention affected by power, refresh rates, temperature), or incomplete implementation of secure erase procedures. This enables unauthorized access to sensitive information after device reset or repurposing.

Risk

Remanent data has severe implications. Confidential data recoverable after erase. Previous owner data accessible. Cryptographic keys extractable. Authentication credentials exposed. Privacy violations. Regulatory compliance failures. Device resale risks. Cold boot attacks enabled. Memory forensics possible. High risk when devices change ownership or are decommissioned.

Solution

Implement multi-cycle memory overwriting with known patterns before erase during architecture and design phase. Use cryptographic erase in self-encrypting memory devices. Apply physical erase tools (e.g., UV-based EEPROM erase). Use physical destruction for decommissioned devices. Test memory contents post-erase operations. Perform architecture and design review of clear and erase implementations.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Confidential data readable by untrusted agents after memory erase.

Example Code

Vulnerable Code

// Vulnerable: Memory with incomplete erase

module vulnerable_memory_controller (
    input  wire        clk,
    input  wire        rst_n,

    // Memory interface
    input  wire [15:0] addr,
    input  wire [31:0] wdata,
    input  wire        write_en,
    input  wire        read_en,
    input  wire        erase_cmd,
    output reg  [31:0] rdata,
    output reg         erase_complete
);

    // Memory array
    reg [31:0] memory [0:65535];

    // VULNERABLE: Erase only clears metadata, not content
    reg memory_valid;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            memory_valid <= 1'b0;
            erase_complete <= 1'b0;
        end else if (erase_cmd) begin
            // VULNERABLE: Only marks memory as invalid
            // Actual data remains in memory cells!
            memory_valid <= 1'b0;
            erase_complete <= 1'b1;

            // Data at memory[*] is NOT overwritten
            // Can be recovered by direct memory access
        end else if (write_en) begin
            memory[addr] <= wdata;
            memory_valid <= 1'b1;
        end
    end

    always @(posedge clk) begin
        if (read_en) begin
            // VULNERABLE: Returns data even if "erased"
            rdata <= memory[addr];
        end
    end

    // Attack:
    // 1. Sensitive data written to memory
    // 2. Erase command issued
    // 3. Memory appears erased (valid bit cleared)
    // 4. Attacker reads memory directly
    // 5. Original data still present!

endmodule

// Vulnerable: SRAM without secure clear
module vulnerable_sram (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [9:0]  addr,
    input  wire [31:0] din,
    input  wire        we,
    input  wire        clear,  // Erase command
    output reg  [31:0] dout
);

    reg [31:0] mem [0:1023];

    always @(posedge clk) begin
        if (we) begin
            mem[addr] <= din;
        end else if (clear) begin
            // VULNERABLE: Clear does nothing!
            // Or only clears one location
            // mem[addr] <= 32'b0;  // Only clears one address
        end

        dout <= mem[addr];
    end

    // VULNERABLE: No mechanism to securely erase all contents
    // Data persists until explicitly overwritten

endmodule
// Vulnerable: Software memory handling with remanence

#include <stdlib.h>
#include <string.h>

// VULNERABLE: Simple free doesn't clear memory
void vulnerable_free_key(uint8_t* key, size_t len) {
    // VULNERABLE: free() doesn't clear memory contents
    free(key);

    // Key data still in memory!
    // Next allocation may return same memory with key visible
}

// VULNERABLE: memset may be optimized away
void vulnerable_clear_buffer(uint8_t* buffer, size_t len) {
    // VULNERABLE: Compiler may optimize this away
    // since buffer is not used after clearing
    memset(buffer, 0, len);

    free(buffer);

    // Compiler optimization may remove memset!
    // Memory contains original data
}

// VULNERABLE: Stack data persists
void vulnerable_process_password(const char* password) {
    char local_copy[256];
    strcpy(local_copy, password);

    // Process password...

    // VULNERABLE: No clearing of local_copy
    // Password remains on stack after function returns
}

// VULNERABLE: Incomplete factory reset
void vulnerable_factory_reset(void) {
    // VULNERABLE: Only resets configuration
    // Does not erase user data

    reset_configuration_to_defaults();

    // VULNERABLE: User data, wifi passwords, etc. remain
    // Previous owner's data accessible to new owner
}

Fixed Code

// Fixed: Memory with secure erase

module secure_memory_controller (
    input  wire        clk,
    input  wire        rst_n,

    // Memory interface
    input  wire [15:0] addr,
    input  wire [31:0] wdata,
    input  wire        write_en,
    input  wire        read_en,
    input  wire        secure_erase_cmd,
    output reg  [31:0] rdata,
    output reg         erase_complete,
    output reg         erase_in_progress
);

    // Memory array
    reg [31:0] memory [0:65535];

    // FIXED: Erase state machine
    reg [15:0] erase_addr;
    reg [1:0] erase_pass;  // Multiple overwrite passes

    localparam ERASE_IDLE = 0;
    localparam ERASE_PASS1 = 1;  // Write all 1s
    localparam ERASE_PASS2 = 2;  // Write all 0s
    localparam ERASE_PASS3 = 3;  // Write random
    localparam ERASE_DONE = 4;

    reg [2:0] erase_state;

    // FIXED: LFSR for pseudo-random pattern
    reg [31:0] lfsr;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            erase_state <= ERASE_IDLE;
            erase_complete <= 1'b0;
            erase_in_progress <= 1'b0;
            lfsr <= 32'hDEADBEEF;
        end else begin
            case (erase_state)
                ERASE_IDLE: begin
                    erase_complete <= 1'b0;
                    if (secure_erase_cmd) begin
                        erase_state <= ERASE_PASS1;
                        erase_addr <= 16'b0;
                        erase_in_progress <= 1'b1;
                    end
                end

                ERASE_PASS1: begin
                    // FIXED: Write all 1s
                    memory[erase_addr] <= 32'hFFFFFFFF;
                    erase_addr <= erase_addr + 1;
                    if (erase_addr == 16'hFFFF) begin
                        erase_state <= ERASE_PASS2;
                        erase_addr <= 16'b0;
                    end
                end

                ERASE_PASS2: begin
                    // FIXED: Write all 0s
                    memory[erase_addr] <= 32'h00000000;
                    erase_addr <= erase_addr + 1;
                    if (erase_addr == 16'hFFFF) begin
                        erase_state <= ERASE_PASS3;
                        erase_addr <= 16'b0;
                    end
                end

                ERASE_PASS3: begin
                    // FIXED: Write pseudo-random pattern
                    memory[erase_addr] <= lfsr;
                    lfsr <= {lfsr[30:0], lfsr[31] ^ lfsr[21] ^ lfsr[1] ^ lfsr[0]};
                    erase_addr <= erase_addr + 1;
                    if (erase_addr == 16'hFFFF) begin
                        erase_state <= ERASE_DONE;
                    end
                end

                ERASE_DONE: begin
                    // FIXED: Final pass - write zeros
                    memory[erase_addr] <= 32'h00000000;
                    erase_complete <= 1'b1;
                    erase_in_progress <= 1'b0;
                    erase_state <= ERASE_IDLE;
                end
            endcase
        end
    end

    // FIXED: Block reads during erase
    always @(posedge clk) begin
        if (read_en && !erase_in_progress) begin
            rdata <= memory[addr];
        end else begin
            rdata <= 32'b0;
        end
    end

    // FIXED: Block writes during erase
    always @(posedge clk) begin
        if (write_en && !erase_in_progress) begin
            memory[addr] <= wdata;
        end
    end

endmodule

// Fixed: Self-encrypting memory with cryptographic erase
module secure_encrypting_memory (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [15:0] addr,
    input  wire [31:0] din,
    input  wire        we,
    input  wire        crypto_erase,  // FIXED: Cryptographic erase
    output reg  [31:0] dout,
    output reg         erase_complete
);

    reg [31:0] mem [0:65535];
    reg [255:0] encryption_key;
    reg key_valid;

    // FIXED: Generate new random key on erase
    // Old data becomes unrecoverable without old key
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            encryption_key <= generate_random_key();
            key_valid <= 1'b1;
            erase_complete <= 1'b0;
        end else if (crypto_erase) begin
            // FIXED: Generate new key - instant secure erase!
            // All data encrypted with old key is now unreadable
            encryption_key <= generate_random_key();
            erase_complete <= 1'b1;

            // Physical memory still contains ciphertext
            // But without old key, it's cryptographically erased
        end else begin
            erase_complete <= 1'b0;
        end
    end

    // Data is always stored encrypted
    always @(posedge clk) begin
        if (we) begin
            mem[addr] <= encrypt(din, encryption_key);
        end
        dout <= decrypt(mem[addr], encryption_key);
    end

endmodule
// Fixed: Software memory handling without remanence

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

// FIXED: Secure memory clear that won't be optimized away
static void secure_memzero(void* ptr, size_t len) {
    volatile uint8_t* p = (volatile uint8_t*)ptr;
    while (len--) {
        *p++ = 0;
    }
    // Memory barrier to prevent reordering
    __asm__ __volatile__("" ::: "memory");
}

// Alternative: Use explicit_bzero if available
#ifdef __GLIBC__
#include <string.h>
#define secure_clear explicit_bzero
#else
#define secure_clear secure_memzero
#endif

// FIXED: Secure key deallocation
void secure_free_key(uint8_t* key, size_t len) {
    if (key) {
        // FIXED: Clear before freeing
        secure_clear(key, len);
        free(key);
    }
}

// FIXED: Secure buffer handling
void secure_clear_buffer(uint8_t* buffer, size_t len) {
    // FIXED: Use volatile to prevent optimization
    secure_clear(buffer, len);
    free(buffer);
}

// FIXED: Secure password handling on stack
void secure_process_password(const char* password) {
    char local_copy[256];
    size_t len = strlen(password);

    // Defensive: limit copy size
    if (len >= sizeof(local_copy)) {
        len = sizeof(local_copy) - 1;
    }

    memcpy(local_copy, password, len);
    local_copy[len] = '\0';

    // Process password...
    process_authentication(local_copy);

    // FIXED: Clear stack data before returning
    secure_clear(local_copy, sizeof(local_copy));
}

// FIXED: Comprehensive factory reset
void secure_factory_reset(void) {
    // FIXED: Multi-pass data wipe following NIST guidelines

    // Erase user data
    secure_erase_partition("/data");

    // Erase cache
    secure_erase_partition("/cache");

    // Erase secure storage
    secure_erase_secure_element();

    // Reset configuration
    reset_configuration_to_defaults();

    // Verify erasure
    if (!verify_data_erased("/data")) {
        // Additional wipe passes if needed
        secure_erase_partition_extended("/data", 3);
    }
}

// FIXED: NIST SP 800-88 compliant erase
void secure_erase_partition(const char* partition) {
    // Open partition for raw access
    int fd = open(partition, O_WRONLY);

    // Get partition size
    size_t size = get_partition_size(partition);

    // FIXED: Multiple overwrite passes
    uint8_t patterns[] = {0x00, 0xFF, 0x00};  // Simplified

    for (int pass = 0; pass < sizeof(patterns); pass++) {
        lseek(fd, 0, SEEK_SET);

        // Write pattern across entire partition
        uint8_t buffer[4096];
        memset(buffer, patterns[pass], sizeof(buffer));

        for (size_t written = 0; written < size; written += sizeof(buffer)) {
            write(fd, buffer, sizeof(buffer));
        }

        // Force write to media
        fsync(fd);
    }

    close(fd);
}

CVE Examples

  • CVE-2019-8575: Apple AirPort Extreme/Time Capsule factory reset vulnerability allowing retrieval of previous owner's wireless network name and WPA2 key.
  • CVE-2020-1938: Memory disclosure through incomplete buffer clearing in Apache Tomcat.

  • CWE-1301: Insufficient or Incomplete Data Removal within Hardware Component (parent)
  • CWE-226: Sensitive Information in Resource Not Removed Before Reuse (related)
  • CWE-244: Improper Clearing of Heap Memory Before Release (related)

References

  1. MITRE Corporation. "CWE-1330: Remanent Data Readable after Memory Erase." https://cwe.mitre.org/data/definitions/1330.html
  2. NIST SP 800-88 Revision 1. "Guidelines for Media Sanitization" (2014)
  3. DoD 5220.22-M. "National Industrial Security Program Operating Manual"