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

ImpactDetails
ConfidentialityScope: Confidentiality

Unauthorized Data Access - Stale mirrored data may allow access to protected resources.
IntegrityScope: Integrity

Data Corruption - Inconsistent mirrors may cause incorrect computations or security decisions.
Access ControlScope: 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.


  • 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

  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