Access Control Check Implemented After Asset is Accessed
Description
Access Control Check Implemented After Asset is Accessed occurs when a product's hardware-based access control check happens after the asset has already been accessed. The vulnerability occurs when access control verification happens non-atomically, allowing asset access before authorization completes, potentially compromising system security. This timing issue means that by the time access is denied, the sensitive data may have already been read or modified.
Risk
Post-access authorization has severe security implications. Sensitive data may be read before denial. Memory may be modified before rejection. Privilege checks become ineffective. Race conditions can be exploited. Authorization decisions are meaningless. Side-channel information may leak. Attackers can extract data bit-by-bit. Security boundaries are violated.
Solution
Execute the access control check first, permitting asset access only after authorization verification succeeds. Use atomic operations that combine the check and access. Ensure that data paths do not forward values before access control completes. Design hardware to gate data outputs until authorization is confirmed. Verify correct ordering through simulation and formal verification.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Bypass Protection Mechanism - Access control is ineffective. |
| Confidentiality | Scope: Confidentiality Read Memory - Data accessible before denial. |
| Integrity | Scope: Integrity Modify Memory - Writes may complete before rejection. |
| Authorization | Scope: Authorization Gain Privileges - Unauthorized access succeeds partially. |
Example Code
Vulnerable Code
// Vulnerable: Access check after data read
module vulnerable_register_access (
input wire clk,
input wire reset_n,
input wire [7:0] addr,
input wire read_enable,
input wire write_enable,
input wire [31:0] write_data,
input wire [3:0] requester_id,
output reg [31:0] read_data,
output reg access_denied
);
// Sensitive register storage
reg [31:0] registers [0:255];
// Access control policy
reg [15:0] read_policy [0:255]; // Bit per requester
reg [15:0] write_policy [0:255];
// VULNERABLE: Using blocking assignments in wrong order
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
read_data <= 32'h0;
access_denied <= 1'b0;
end
else if (read_enable) begin
// VULNERABLE: Data read happens FIRST
read_data = registers[addr]; // Blocking assignment - immediate
// Access check happens AFTER
if (!read_policy[addr][requester_id]) begin
access_denied <= 1'b1;
read_data <= 32'hDEADDEAD; // Try to mask - too late!
end
else begin
access_denied <= 1'b0;
end
// Problem: read_data was already updated with sensitive value
// Even if access_denied, the value was momentarily visible
end
end
endmodule
// Vulnerable: Write before access check
module vulnerable_memory_write (
input wire clk,
input wire reset_n,
input wire [15:0] addr,
input wire [31:0] write_data,
input wire write_enable,
input wire [3:0] master_id,
output reg write_complete,
output reg access_denied
);
reg [31:0] memory [0:65535];
// MPU configuration
reg [31:0] mpu_start [0:7];
reg [31:0] mpu_end [0:7];
reg [15:0] mpu_allowed_masters [0:7];
function automatic is_access_allowed;
input [15:0] address;
input [3:0] master;
integer i;
begin
is_access_allowed = 1'b0;
for (i = 0; i < 8; i = i + 1) begin
if (address >= mpu_start[i] && address <= mpu_end[i]) begin
if (mpu_allowed_masters[i][master]) begin
is_access_allowed = 1'b1;
end
end
end
end
endfunction
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
write_complete <= 1'b0;
access_denied <= 1'b0;
end
else if (write_enable) begin
// VULNERABLE: Write happens immediately
memory[addr] = write_data; // Blocking - immediate effect
// Check happens after write is done
if (!is_access_allowed(addr, master_id)) begin
access_denied <= 1'b1;
// VULNERABLE: Data already written!
// Trying to undo is too late
memory[addr] = 32'h0; // Attempt to clear
end
else begin
access_denied <= 1'b0;
end
write_complete <= 1'b1;
end
end
endmodule
// Vulnerable: Software access check after read
#include <stdint.h>
typedef struct {
uint32_t data[256];
uint8_t access_policy[256]; // Bit 0 = read, Bit 1 = write
} protected_region_t;
// VULNERABLE: Read then check
uint32_t vulnerable_read(protected_region_t* region, uint8_t index,
uint8_t requester_privilege) {
// VULNERABLE: Read the data first
uint32_t value = region->data[index];
// Check happens after
if (!(region->access_policy[index] & 0x01)) {
// Access denied - but value already in 'value' variable
// Could be leaked through timing, cache, etc.
return 0xFFFFFFFF; // Return error value
}
// Also check privilege (after read)
if (requester_privilege < get_required_privilege(index)) {
// Denied - but value was already read
return 0xFFFFFFFF;
}
return value;
// Attack: Even on denial, value was loaded into register
// Side channels may leak the actual value
}
// VULNERABLE: Write then check
void vulnerable_write(protected_region_t* region, uint8_t index,
uint32_t value, uint8_t requester_privilege) {
// VULNERABLE: Write first
region->data[index] = value;
// Check permissions after write
if (!(region->access_policy[index] & 0x02)) {
// Oops! Already wrote - try to undo
region->data[index] = 0; // But damage may be done
}
}
Fixed Code
// Fixed: Access check before data read
module secure_register_access (
input wire clk,
input wire reset_n,
input wire [7:0] addr,
input wire read_enable,
input wire write_enable,
input wire [31:0] write_data,
input wire [3:0] requester_id,
output reg [31:0] read_data,
output reg access_denied,
output reg operation_complete
);
// Sensitive register storage
reg [31:0] registers [0:255];
// Access control policy
reg [15:0] read_policy [0:255];
reg [15:0] write_policy [0:255];
// FIXED: Check access permission combinationally
wire read_allowed = read_policy[addr][requester_id];
wire write_allowed = write_policy[addr][requester_id];
// FIXED: State machine for controlled access
reg [1:0] state;
parameter IDLE = 2'd0;
parameter CHECK = 2'd1;
parameter ACCESS = 2'd2;
parameter COMPLETE = 2'd3;
reg pending_read;
reg pending_write;
reg [7:0] pending_addr;
reg [31:0] pending_data;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
read_data <= 32'h0;
access_denied <= 1'b0;
operation_complete <= 1'b0;
state <= IDLE;
pending_read <= 1'b0;
pending_write <= 1'b0;
end
else begin
case (state)
IDLE: begin
access_denied <= 1'b0;
operation_complete <= 1'b0;
if (read_enable || write_enable) begin
pending_read <= read_enable;
pending_write <= write_enable;
pending_addr <= addr;
pending_data <= write_data;
state <= CHECK;
end
end
CHECK: begin
// FIXED: Check access FIRST, before any data movement
if (pending_read && !read_allowed) begin
access_denied <= 1'b1;
read_data <= 32'h0; // No real data ever loaded
state <= COMPLETE;
end
else if (pending_write && !write_allowed) begin
access_denied <= 1'b1;
state <= COMPLETE;
end
else begin
state <= ACCESS; // Permission granted
end
end
ACCESS: begin
// FIXED: Only access data after permission verified
if (pending_read) begin
read_data <= registers[pending_addr];
end
else if (pending_write) begin
registers[pending_addr] <= pending_data;
end
state <= COMPLETE;
end
COMPLETE: begin
operation_complete <= 1'b1;
pending_read <= 1'b0;
pending_write <= 1'b0;
state <= IDLE;
end
endcase
end
end
endmodule
// Fixed: Atomic access control with gated output
module secure_gated_access (
input wire clk,
input wire reset_n,
input wire [7:0] addr,
input wire read_enable,
input wire [3:0] requester_id,
output wire [31:0] read_data,
output wire access_denied
);
reg [31:0] registers [0:255];
reg [15:0] read_policy [0:255];
// FIXED: Combinational access check
wire allowed = read_policy[addr][requester_id];
// FIXED: Gate data output based on access permission
// Data path is blocked until permission verified
assign read_data = (read_enable && allowed) ? registers[addr] : 32'h0;
assign access_denied = read_enable && !allowed;
// No timing gap - check and gating are combinational
endmodule
// Fixed: Memory protection with check-before-access
module secure_memory_protection (
input wire clk,
input wire reset_n,
input wire [15:0] addr,
input wire [31:0] write_data,
input wire write_enable,
input wire read_enable,
input wire [3:0] master_id,
output reg [31:0] read_data,
output reg operation_complete,
output reg access_denied
);
reg [31:0] memory [0:65535];
// MPU configuration
reg [15:0] region_start [0:7];
reg [15:0] region_end [0:7];
reg [15:0] region_read_mask [0:7]; // Masters allowed to read
reg [15:0] region_write_mask [0:7]; // Masters allowed to write
// FIXED: Pre-compute access permission
function automatic check_permission;
input [15:0] address;
input [3:0] master;
input is_write;
integer i;
begin
check_permission = 1'b0;
for (i = 0; i < 8; i = i + 1) begin
if (address >= region_start[i] && address <= region_end[i]) begin
if (is_write) begin
check_permission = region_write_mask[i][master];
end
else begin
check_permission = region_read_mask[i][master];
end
end
end
end
endfunction
// State machine
reg [1:0] state;
parameter IDLE = 2'd0;
parameter CHECKING = 2'd1;
parameter ACCESSING = 2'd2;
reg [15:0] latched_addr;
reg [31:0] latched_data;
reg latched_write;
reg access_allowed;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
state <= IDLE;
read_data <= 32'h0;
operation_complete <= 1'b0;
access_denied <= 1'b0;
end
else begin
case (state)
IDLE: begin
operation_complete <= 1'b0;
access_denied <= 1'b0;
if (write_enable || read_enable) begin
latched_addr <= addr;
latched_data <= write_data;
latched_write <= write_enable;
state <= CHECKING;
end
end
CHECKING: begin
// FIXED: Evaluate permission BEFORE accessing memory
access_allowed <= check_permission(latched_addr, master_id,
latched_write);
if (!check_permission(latched_addr, master_id, latched_write)) begin
// FIXED: Deny before any access
access_denied <= 1'b1;
operation_complete <= 1'b1;
state <= IDLE;
end
else begin
state <= ACCESSING;
end
end
ACCESSING: begin
// FIXED: Only reach here if access was allowed
if (latched_write) begin
memory[latched_addr] <= latched_data;
end
else begin
read_data <= memory[latched_addr];
end
operation_complete <= 1'b1;
state <= IDLE;
end
endcase
end
end
endmodule
// Fixed: Software access check before read
#include <stdint.h>
#include <stdbool.h>
typedef struct {
uint32_t data[256];
uint8_t access_policy[256];
} protected_region_t;
// FIXED: Check then read
bool secure_read(protected_region_t* region, uint8_t index,
uint8_t requester_privilege, uint32_t* value_out) {
// FIXED: Check permissions FIRST
if (!(region->access_policy[index] & 0x01)) {
*value_out = 0;
return false; // Access denied - no read performed
}
// FIXED: Check privilege BEFORE read
if (requester_privilege < get_required_privilege(index)) {
*value_out = 0;
return false; // Privilege denied - no read performed
}
// FIXED: Only now perform the read
*value_out = region->data[index];
return true;
}
// FIXED: Check then write
bool secure_write(protected_region_t* region, uint8_t index,
uint32_t value, uint8_t requester_privilege) {
// FIXED: Check write permission FIRST
if (!(region->access_policy[index] & 0x02)) {
return false; // Access denied - no write performed
}
// FIXED: Check privilege BEFORE write
if (requester_privilege < get_required_privilege(index)) {
return false; // Privilege denied - no write performed
}
// FIXED: Only now perform the write
region->data[index] = value;
return true;
}
// FIXED: Atomic check-and-access for hardware registers
uint32_t secure_hw_read(uint32_t addr, uint8_t requester) {
// FIXED: Ask hardware to perform atomic check-and-read
// Hardware ensures data is not visible until check passes
// Set up request
write_hw_reg(ACCESS_CTRL_ADDR, addr);
write_hw_reg(ACCESS_CTRL_REQUESTER, requester);
write_hw_reg(ACCESS_CTRL_CMD, CMD_READ);
// Wait for completion
while (!(read_hw_reg(ACCESS_CTRL_STATUS) & STATUS_COMPLETE)) {
// Wait
}
// Check result
if (read_hw_reg(ACCESS_CTRL_STATUS) & STATUS_DENIED) {
return 0; // Access was denied
}
// Access was allowed - read result
return read_hw_reg(ACCESS_CTRL_DATA);
}
CVE Examples
Access control timing vulnerabilities have been found in various hardware designs where data was exposed before permission checks completed, enabling information disclosure through side channels or direct observation.
Related CWEs
- CWE-284: Improper Access Control (parent)
- CWE-696: Incorrect Behavior Order (parent)
- CWE-1198: Privilege Separation and Access Control Issues (category)
- CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition (related)
- CAPEC-180: Exploiting Incorrectly Configured Access Control Security Levels (attack pattern)
References
- MITRE Corporation. "CWE-1280: Access Control Check Implemented After Asset is Accessed." https://cwe.mitre.org/data/definitions/1280.html
- ARM. "AMBA AXI Protocol Specification"
- IEEE. "Hardware Security Best Practices"