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
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Protected data readable through unprotected mirror addresses. |
| Integrity | Scope: Integrity Protected memory modifiable via mirror writes. |
| Access Control | Scope: 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.
Related CWEs
- 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
- MITRE Corporation. "CWE-1312: Missing Protection for Mirrored Regions in On-Chip Fabric Firewall." https://cwe.mitre.org/data/definitions/1312.html
- ARM. "TrustZone Address Space Controller Technical Reference Manual"
- Intel. "System Address Map Configuration"