Missing Protection for Mirrored Regions in On-Chip Fabric Firewall

Description

Missing Protection for Mirrored Regions in On-Chip Fabric Firewall occurs when the firewall in an on-chip fabric protects the main addressed region but does not protect any mirrored memory or memory-mapped-IO (MMIO) regions. Systems sometimes employ mirrored memory and address ranges containing data copies for fault tolerance purposes. When fabric firewalls protect original regions but neglect mirrored ones, attackers can circumvent access controls by targeting the unprotected mirrors. This enables bypassing read/write protections to leak or corrupt underlying data.

Risk

Missing mirror protection has severe implications. Access control completely bypassed via mirrors. Protected memory readable through unprotected alias. Memory corruption through mirrored writes. Firewall security rendered ineffective. Confidential data leaked. System integrity compromised. High likelihood when mirrors exist and aren't protected.

Solution

The fabric firewall should apply the same protections as the original region to the mirrored regions during architecture and design phase. Apply identical protections to mirrored regions as enforced on original regions during implementation. Enumerate all address aliases and mirrors during design. Configure firewall rules to cover all address ranges that map to protected data. Verify protection through testing with mirror addresses.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Protected data readable through unprotected mirror addresses.
IntegrityScope: Integrity

Protected memory modifiable via mirror writes.
Access ControlScope: Access Control

Firewall protection mechanism completely bypassed.

Example Code

Vulnerable Code

// Vulnerable: Fabric firewall without mirror protection

module vulnerable_fabric_firewall (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [31:0] addr,
    input  wire [31:0] wdata,
    input  wire        write_en,
    input  wire        read_en,
    input  wire [1:0]  security_level,
    output reg  [31:0] rdata,
    output reg         access_granted,
    output reg         access_denied
);

    // Memory controller creates mirrors using upper address bits
    // Original region: 0x0000_0000 - 0x0000_FFFF (bits [31:16] = 00)
    // Mirror 1:        0x0001_0000 - 0x0001_FFFF (bits [31:16] = 01)
    // Mirror 2:        0x0002_0000 - 0x0002_FFFF (bits [31:16] = 02)
    // Mirror 3:        0x0003_0000 - 0x0003_FFFF (bits [31:16] = 03)

    // All mirrors map to same physical memory

    // Protected region within original space
    localparam PROTECTED_START = 32'h0000_8000;
    localparam PROTECTED_END   = 32'h0000_8FFF;
    localparam REQUIRED_LEVEL  = 2'b10;  // Privileged access required

    // VULNERABLE: Only checking original address range
    wire in_protected_region = (addr >= PROTECTED_START) &&
                               (addr <= PROTECTED_END);

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            access_granted <= 1'b0;
            access_denied <= 1'b0;
        end else begin
            access_granted <= 1'b0;
            access_denied <= 1'b0;

            if (read_en || write_en) begin
                if (in_protected_region) begin
                    // VULNERABLE: Protection only on original region
                    if (security_level >= REQUIRED_LEVEL) begin
                        access_granted <= 1'b1;
                    end else begin
                        access_denied <= 1'b1;
                    end
                end else begin
                    // Non-protected region - allow access
                    access_granted <= 1'b1;
                end
            end
        end
    end

    // Attack:
    // Protected address: 0x0000_8000 - requires level 2
    // Attacker uses mirror: 0x0001_8000 - no protection check!
    // Access granted because 0x0001_8000 is NOT in protected range
    // But memory controller maps it to same physical location!

endmodule

// Vulnerable: Memory controller with unprotected aliases
module vulnerable_memory_controller (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [31:0] addr,
    input  wire [31:0] wdata,
    input  wire        write_en,
    input  wire        read_en,
    output reg  [31:0] rdata
);

    // Physical memory
    reg [31:0] memory [0:65535];

    // Address aliasing - bits [31:16] ignored
    wire [15:0] physical_addr = addr[15:0];

    // VULNERABLE: All address ranges access same physical memory
    // No distinction between original and mirrors
    // If firewall doesn't protect mirrors, security bypassed

    always @(posedge clk) begin
        if (write_en) begin
            memory[physical_addr] <= wdata;
        end
        if (read_en) begin
            rdata <= memory[physical_addr];
        end
    end

    // Memory map:
    // 0x0000_XXXX -> memory[XXXX] (original)
    // 0x0001_XXXX -> memory[XXXX] (mirror 1)
    // 0x0002_XXXX -> memory[XXXX] (mirror 2)
    // 0x0003_XXXX -> memory[XXXX] (mirror 3)
    //
    // All map to same physical location!

endmodule
// Vulnerable: Software firewall configuration without mirrors

#include <stdint.h>
#include <stdbool.h>

typedef struct {
    uint32_t start_addr;
    uint32_t end_addr;
    uint8_t  required_level;
} firewall_rule_t;

// VULNERABLE: Only protects original region
static firewall_rule_t firewall_rules[] = {
    {0x00008000, 0x00008FFF, 2},  // Protected region
    // VULNERABLE: No rules for mirror regions
    // 0x00018000-0x00018FFF (mirror 1) - unprotected!
    // 0x00028000-0x00028FFF (mirror 2) - unprotected!
    // 0x00038000-0x00038FFF (mirror 3) - unprotected!
};

bool vulnerable_check_access(uint32_t addr, uint8_t security_level) {
    for (int i = 0; i < sizeof(firewall_rules)/sizeof(firewall_rules[0]); i++) {
        if (addr >= firewall_rules[i].start_addr &&
            addr <= firewall_rules[i].end_addr) {
            // VULNERABLE: Only checks exact address match
            return security_level >= firewall_rules[i].required_level;
        }
    }

    // No matching rule - allow access
    // VULNERABLE: Mirror addresses fall through here
    return true;
}

Fixed Code

// Fixed: Fabric firewall with mirror protection

module secure_fabric_firewall (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [31:0] addr,
    input  wire [31:0] wdata,
    input  wire        write_en,
    input  wire        read_en,
    input  wire [1:0]  security_level,
    output reg  [31:0] rdata,
    output reg         access_granted,
    output reg         access_denied
);

    // Memory layout with mirrors
    // Original region: 0x0000_0000 - 0x0000_FFFF (bits [31:16] = 00)
    // Mirror 1:        0x0001_0000 - 0x0001_FFFF (bits [31:16] = 01)
    // Mirror 2:        0x0002_0000 - 0x0002_FFFF (bits [31:16] = 02)
    // Mirror 3:        0x0003_0000 - 0x0003_FFFF (bits [31:16] = 03)

    // Protected region (applies to all mirrors)
    localparam PROTECTED_OFFSET_START = 16'h8000;
    localparam PROTECTED_OFFSET_END   = 16'h8FFF;
    localparam REQUIRED_LEVEL         = 2'b10;

    // FIXED: Extract physical address (ignore mirror bits)
    wire [15:0] physical_offset = addr[15:0];

    // FIXED: Check if physical offset is in protected range
    // This catches accesses via any mirror address
    wire in_protected_region = (physical_offset >= PROTECTED_OFFSET_START) &&
                               (physical_offset <= PROTECTED_OFFSET_END);

    // FIXED: Verify address is in valid memory space (any mirror)
    wire in_memory_space = (addr[31:18] == 14'b0);  // First 4 regions

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            access_granted <= 1'b0;
            access_denied <= 1'b0;
        end else begin
            access_granted <= 1'b0;
            access_denied <= 1'b0;

            if ((read_en || write_en) && in_memory_space) begin
                if (in_protected_region) begin
                    // FIXED: Protection applies to all mirrors
                    if (security_level >= REQUIRED_LEVEL) begin
                        access_granted <= 1'b1;
                    end else begin
                        access_denied <= 1'b1;
                    end
                end else begin
                    access_granted <= 1'b1;
                end
            end else if (read_en || write_en) begin
                // Outside valid memory space
                access_denied <= 1'b1;
            end
        end
    end

endmodule

// Fixed: Comprehensive firewall with alias awareness
module secure_alias_aware_firewall #(
    parameter NUM_MIRRORS = 4,
    parameter MIRROR_BITS = 2  // Number of bits used for mirror selection
) (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [31:0] addr,
    input  wire [31:0] wdata,
    input  wire        write_en,
    input  wire        read_en,
    input  wire [1:0]  security_level,
    output reg         access_granted,
    output reg         access_denied
);

    // FIXED: Firewall rules defined in terms of physical addresses
    // Protection applies regardless of which mirror is accessed

    // Rule format: {start_offset, end_offset, required_level}
    reg [47:0] firewall_rules [0:7];
    reg [2:0] num_rules;

    initial begin
        // Protected region 1: 0x8000-0x8FFF, level 2
        firewall_rules[0] = {16'h8000, 16'h8FFF, 16'h0002};
        // Protected region 2: 0xF000-0xFFFF, level 3
        firewall_rules[1] = {16'hF000, 16'hFFFF, 16'h0003};
        num_rules = 2;
    end

    // FIXED: Convert any address to canonical physical offset
    wire [15:0] canonical_offset = addr[15:0];

    // FIXED: Check address against all rules using canonical offset
    reg rule_match;
    reg [1:0] required_level;

    integer i;
    always @(*) begin
        rule_match = 1'b0;
        required_level = 2'b00;

        for (i = 0; i < num_rules; i = i + 1) begin
            if (canonical_offset >= firewall_rules[i][47:32] &&
                canonical_offset <= firewall_rules[i][31:16]) begin
                rule_match = 1'b1;
                required_level = firewall_rules[i][1:0];
            end
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            access_granted <= 1'b0;
            access_denied <= 1'b0;
        end else begin
            access_granted <= 1'b0;
            access_denied <= 1'b0;

            if (read_en || write_en) begin
                if (rule_match) begin
                    // FIXED: Enforced for all mirrors
                    if (security_level >= required_level) begin
                        access_granted <= 1'b1;
                    end else begin
                        access_denied <= 1'b1;
                    end
                end else begin
                    access_granted <= 1'b1;
                end
            end
        end
    end

endmodule
// Fixed: Software firewall with mirror awareness

#include <stdint.h>
#include <stdbool.h>

typedef struct {
    uint16_t start_offset;   // Physical offset (not full address)
    uint16_t end_offset;
    uint8_t  required_level;
} secure_firewall_rule_t;

// FIXED: Rules defined in terms of physical offsets
static secure_firewall_rule_t firewall_rules[] = {
    {0x8000, 0x8FFF, 2},  // Protected region 1
    {0xF000, 0xFFFF, 3},  // Protected region 2
};

// FIXED: Convert address to canonical physical offset
static uint16_t get_physical_offset(uint32_t addr) {
    // Bits [15:0] are the physical offset
    // Bits [17:16] select which mirror (ignored for protection)
    return addr & 0xFFFF;
}

// FIXED: Validate address is in memory space
static bool is_valid_memory_address(uint32_t addr) {
    // Memory space is 0x00000000 - 0x0003FFFF (4 mirrors of 64KB)
    return addr < 0x00040000;
}

bool secure_check_access(uint32_t addr, uint8_t security_level) {
    // FIXED: First validate address
    if (!is_valid_memory_address(addr)) {
        return false;
    }

    // FIXED: Get canonical physical offset
    uint16_t offset = get_physical_offset(addr);

    // FIXED: Check all rules using physical offset
    for (size_t i = 0; i < sizeof(firewall_rules)/sizeof(firewall_rules[0]); i++) {
        if (offset >= firewall_rules[i].start_offset &&
            offset <= firewall_rules[i].end_offset) {
            // FIXED: Protection applies to all mirrors
            return security_level >= firewall_rules[i].required_level;
        }
    }

    // No rule matched - allow access
    return true;
}

// FIXED: Verification function to test mirror protection
bool verify_mirror_protection(void) {
    // Test that protection works for all mirrors
    uint32_t protected_offset = 0x8000;

    for (int mirror = 0; mirror < 4; mirror++) {
        uint32_t addr = (mirror << 16) | protected_offset;

        // Level 1 should be denied for all mirrors
        if (secure_check_access(addr, 1)) {
            return false;  // Mirror protection failed!
        }

        // Level 2 should be allowed for all mirrors
        if (!secure_check_access(addr, 2)) {
            return false;  // Protection inconsistent!
        }
    }

    return true;  // All mirrors protected equally
}

CVE Examples

  • CVE-2019-0090: Intel CSME memory region accessible through unprotected mirror addresses.
  • CVE-2020-0551: Load value injection through unprotected memory aliases.

  • CWE-284: Improper Access Control (parent)
  • CWE-1251: Mirrored Regions with Different Values (peer)
  • CWE-1203: Peripherals, On-chip Fabric, and Interface/IO Problems (category)
  • CWE-1299: Missing Protection Mechanism for Alternate Hardware Interface (related)

References

  1. MITRE Corporation. "CWE-1312: Missing Protection for Mirrored Regions in On-Chip Fabric Firewall." https://cwe.mitre.org/data/definitions/1312.html
  2. ARM. "TrustZone Address Space Controller Technical Reference Manual"
  3. Intel. "System Address Map Configuration"