Mirrored Regions with Different Values
Description
Mirrored Regions with Different Values occurs when a product's architecture duplicates regions (such as cache memory or shadow copies of registers) without ensuring that their contents always stay in sync. Performance optimization often requires duplicating resources, but the product needs to ensure that the local copy always mirrors the original copy truthfully. When synchronization fails, computational results become unreliable and security mechanisms can be bypassed.
Risk
Desynchronized mirrored regions create severe security implications. Access control may be bypassed via stale permissions. Cache poisoning may lead to privilege escalation. Shadow registers may contain incorrect security state. Memory protection may be circumvented. Cryptographic keys may be inconsistent. Authentication state may differ across components. Attackers can exploit race conditions during synchronization. Security decisions may be based on stale data.
Solution
Minimize out-of-sync time periods and make the update process as robust as possible. Ensure original copies send update notifications. Verify shadow copies execute received updates. Protect against race conditions during update windows. Authenticate update requests to prevent spoofing. Implement completion acknowledgment to prevent reversion. Add integrity verification between original and mirror. Implement atomic update mechanisms.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Unauthorized Data Access - Stale mirrored data may allow access to protected resources. |
| Integrity | Scope: Integrity Data Corruption - Inconsistent mirrors may cause incorrect computations or security decisions. |
| Access Control | Scope: Access Control Bypass Protection Mechanism - Outdated permission data in mirrors may allow unauthorized access. |
Example Code
Vulnerable Code
// Vulnerable: Register shadow copy without synchronization
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 registers
reg [31:0] master_regs [0:255];
// Shadow copy for fast access
reg [31:0] shadow_regs [0:255];
// VULNERABLE: Master update without shadow synchronization
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
// Update master
master_regs[master_addr] <= master_write_data;
// VULNERABLE: Shadow update is asynchronous
// Race condition: shadow may not be updated before next read
end
end
// VULNERABLE: Separate process for shadow update
// Can miss updates or get out of sync
always @(posedge clk) begin
// Background synchronization - may lag behind
shadow_regs[0] <= master_regs[0];
shadow_regs[1] <= master_regs[1];
// ... only syncs a few registers per cycle
end
// Read from potentially stale shadow
always @(*) begin
if (shadow_read_enable) begin
shadow_read_data = shadow_regs[shadow_read_addr];
end
end
endmodule
// Vulnerable: Cache without coherence
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
);
// Main memory
reg [31:0] main_memory [0:1023];
// Cache (mirror of frequently accessed data)
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
// Initialize
integer i;
for (i = 0; i < 64; i = i + 1) begin
cache_valid[i] <= 1'b0;
end
end
else if (mem_write_enable) begin
// VULNERABLE: Write-through without invalidation notification
main_memory[mem_addr[11:2]] <= mem_write_data;
// Update cache if present
if (cache_valid[cache_index] && cache_tags[cache_index] == cache_tag) begin
cache_data[cache_index] <= mem_write_data;
end
// VULNERABLE: Other caches (in multiprocessor) not notified
// They will have stale data
end
end
// Read with potentially stale cache
always @(*) begin
if (mem_read_enable) begin
if (cache_valid[cache_index] && cache_tags[cache_index] == cache_tag) begin
// VULNERABLE: Cache hit but data may be stale
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
// Vulnerable: Memory-mapped register mirror without sync
typedef struct {
volatile uint32_t* master_base;
uint32_t* shadow_copy;
size_t num_registers;
} VulnerableRegisterMirror;
void vulnerable_init(VulnerableRegisterMirror* mirror) {
// VULNERABLE: One-time copy at initialization
for (size_t i = 0; i < mirror->num_registers; i++) {
mirror->shadow_copy[i] = mirror->master_base[i];
}
// Shadow copy will become stale after any master update
}
uint32_t vulnerable_read(VulnerableRegisterMirror* mirror, size_t index) {
// VULNERABLE: Read from shadow without checking freshness
return mirror->shadow_copy[index];
}
void vulnerable_write(VulnerableRegisterMirror* mirror, size_t index, uint32_t value) {
// Write to master
mirror->master_base[index] = value;
// VULNERABLE: Shadow update may be interrupted
// Another thread could read stale shadow between these lines
mirror->shadow_copy[index] = value;
}
// Vulnerable: DMA buffer mirroring
typedef struct {
uint8_t* cpu_buffer;
uint8_t* dma_buffer;
size_t size;
} VulnerableDMAMirror;
void vulnerable_dma_prepare(VulnerableDMAMirror* mirror) {
// VULNERABLE: Copy to DMA buffer without synchronization
memcpy(mirror->dma_buffer, mirror->cpu_buffer, mirror->size);
// DMA engine may see partially updated buffer
// No memory barrier or cache flush
}
void vulnerable_dma_complete(VulnerableDMAMirror* mirror) {
// VULNERABLE: Copy back without synchronization
memcpy(mirror->cpu_buffer, mirror->dma_buffer, mirror->size);
// CPU cache may still have old data
// No cache invalidation
}
Fixed Code
// Fixed: Register shadow with verified synchronization
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 registers
reg [31:0] master_regs [0:255];
// Shadow copy with version tracking
reg [31:0] shadow_regs [0:255];
reg [15:0] master_version [0:255];
reg [15:0] shadow_version [0:255];
// FIXED: Atomic update of master and shadow
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
// FIXED: Update master and shadow atomically
master_regs[master_addr] <= master_write_data;
shadow_regs[master_addr] <= master_write_data;
// Increment versions together
master_version[master_addr] <= master_version[master_addr] + 1;
shadow_version[master_addr] <= master_version[master_addr] + 1;
end
end
// FIXED: Verify sync before returning data
always @(*) begin
if (shadow_read_enable) begin
// Check version match
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
// FIXED: Fail-secure on mismatch
shadow_read_data = 32'h0;
sync_valid = 1'b0;
sync_error = 1'b1;
end
end
end
// FIXED: Periodic integrity check
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
// Fixed: Cache with coherence protocol
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 state (MESI protocol)
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
// FIXED: Handle invalidation requests from other caches
else if (invalidate_req) begin
if (cache_tags[inv_index] == inv_tag &&
cache_state[inv_index] != INVALID) begin
// Write back if modified
if (cache_state[inv_index] == MODIFIED) begin
// Trigger write-back before invalidating
writeback_to_memory(inv_index);
end
cache_state[inv_index] <= INVALID;
end
invalidate_ack <= 1'b1;
end
else if (mem_write_enable) begin
// FIXED: Update with proper state transition
cache_data[cache_index] <= mem_write_data;
cache_tags[cache_index] <= cache_tag;
cache_state[cache_index] <= MODIFIED;
// FIXED: Broadcast invalidation to other caches
// (handled by interconnect)
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 miss - fetch from memory
mem_read_data = fetch_from_memory(mem_addr);
cache_hit = 1'b0;
end
end
end
endmodule
// Fixed: Memory-mapped register mirror with synchronization
#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);
// FIXED: Atomic initialization with version tracking
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;
// FIXED: Seqlock pattern for consistent read
do {
version1 = atomic_load(&mirror->versions[index]);
// Read shadow
value = mirror->shadow_copy[index];
// Memory barrier
atomic_thread_fence(memory_order_acquire);
version2 = atomic_load(&mirror->versions[index]);
// Retry if version changed during read
} while (version1 != version2 || (version1 & 1));
// FIXED: Verify against master periodically
if ((version1 % 100) == 0) {
uint32_t master_value = mirror->master_base[index];
if (value != master_value) {
// Resync and log error
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);
// FIXED: Mark version as odd (write in progress)
unsigned int old_version = atomic_fetch_add(&mirror->versions[index], 1);
// Memory barrier
atomic_thread_fence(memory_order_release);
// Write to both atomically
mirror->master_base[index] = value;
mirror->shadow_copy[index] = value;
// Memory barrier
atomic_thread_fence(memory_order_release);
// Mark version as even (write complete)
atomic_fetch_add(&mirror->versions[index], 1);
pthread_spin_unlock(&mirror->lock);
}
// Fixed: DMA buffer mirroring with proper sync
typedef struct {
uint8_t* cpu_buffer;
uint8_t* dma_buffer;
size_t size;
volatile int sync_state;
} SecureDMAMirror;
void secure_dma_prepare(SecureDMAMirror* mirror) {
// FIXED: Flush CPU cache first
cache_flush(mirror->cpu_buffer, mirror->size);
// Memory barrier
__sync_synchronize();
// Copy to DMA buffer
memcpy(mirror->dma_buffer, mirror->cpu_buffer, mirror->size);
// FIXED: Memory barrier before DMA start
__sync_synchronize();
mirror->sync_state = 1; // DMA ready
}
void secure_dma_complete(SecureDMAMirror* mirror) {
// FIXED: Wait for DMA completion
while (!dma_is_complete()) {
// Busy wait or yield
}
// FIXED: Invalidate CPU cache
cache_invalidate(mirror->cpu_buffer, mirror->size);
// Memory barrier
__sync_synchronize();
// Copy back
memcpy(mirror->cpu_buffer, mirror->dma_buffer, mirror->size);
// Memory barrier after update
__sync_synchronize();
mirror->sync_state = 2; // CPU ready
}
CVE Examples
Mirrored region vulnerabilities have been found in processor cache implementations, TLB entries, and shadow register banks where attackers exploited synchronization gaps to access protected data or bypass access controls.
Related CWEs
- CWE-1250: Improper Preservation of Consistency Between Independent Representations (parent)
- CWE-1312: Missing Protection for Mirrored Regions in On-Chip Fabric Firewall (related)
- CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization (related)
References
- MITRE Corporation. "CWE-1251: Mirrored Regions with Different Values." https://cwe.mitre.org/data/definitions/1251.html
- Patterson & Hennessy. "Computer Organization and Design" - Cache Coherence Protocols
- MESI Protocol Documentation