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
| Impact | Details |
|---|---|
| Confidentiality | Scope: 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.
Related CWEs
- 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
- MITRE Corporation. "CWE-1330: Remanent Data Readable after Memory Erase." https://cwe.mitre.org/data/definitions/1330.html
- NIST SP 800-88 Revision 1. "Guidelines for Media Sanitization" (2014)
- DoD 5220.22-M. "National Industrial Security Program Operating Manual"