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

ImpactDetails
Access ControlScope: Access Control

Bypass Protection Mechanism - Access control is ineffective.
ConfidentialityScope: Confidentiality

Read Memory - Data accessible before denial.
IntegrityScope: Integrity

Modify Memory - Writes may complete before rejection.
AuthorizationScope: 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.


  • 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

  1. MITRE Corporation. "CWE-1280: Access Control Check Implemented After Asset is Accessed." https://cwe.mitre.org/data/definitions/1280.html
  2. ARM. "AMBA AXI Protocol Specification"
  3. IEEE. "Hardware Security Best Practices"