Improper Access Control for Register Interface

Description

Improper Access Control for Register Interface occurs when a product uses memory-mapped I/O registers providing hardware access from software but lacks proper access controls to those registers. Peripherals in Systems-on-Chip (SoC) and other devices are commonly accessed through memory-mapped register interfaces. Without adequate protections, malicious software could compromise security-critical hardware data, leading to loss of confidentiality and integrity.

Risk

Unprotected register interfaces have severe security implications. Cryptographic keys may be readable. Security configurations may be modified. Hardware state may be corrupted. Protection mechanisms may be bypassed. Privilege escalation becomes possible. Security-critical data may be altered. Unexpected hardware states may be triggered. Execution logic may be modified maliciously.

Solution

Establish proper policies for hardware register access from software. Ensure access control policies are correctly implemented per specifications. Implement privilege-level checks for register access. Disable read-back paths for sensitive data like cryptographic keys. Use hardware-enforced access control. Validate all register addresses and values. Implement proper CSR protection in processors.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Read Memory - Attackers may read protected information through unprotected registers.
IntegrityScope: Integrity

Modify Memory - Security state, settings, and critical data may be corrupted or modified.
Access ControlScope: Access Control

Gain Privileges - Unauthorized privilege escalation through register manipulation.

Example Code

Vulnerable Code

// Vulnerable: Cryptographic accelerator with readable keys

module vulnerable_crypto_accelerator (
    input wire clk,
    input wire reset_n,
    input wire [7:0] reg_addr,
    input wire [31:0] reg_write_data,
    input wire reg_write,
    input wire reg_read,
    output reg [31:0] reg_read_data
);

    // Key storage
    reg [255:0] crypto_key;

    // Status and control registers
    reg [31:0] status_reg;
    reg [31:0] control_reg;

    // Register addresses
    parameter KEY_REG_0 = 8'h00;
    parameter KEY_REG_1 = 8'h04;
    parameter KEY_REG_2 = 8'h08;
    parameter KEY_REG_3 = 8'h0C;
    parameter KEY_REG_4 = 8'h10;
    parameter KEY_REG_5 = 8'h14;
    parameter KEY_REG_6 = 8'h18;
    parameter KEY_REG_7 = 8'h1C;
    parameter STATUS_REG = 8'h20;
    parameter CONTROL_REG = 8'h24;

    // Key write
    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            crypto_key <= 256'h0;
        end
        else if (reg_write) begin
            case (reg_addr)
                KEY_REG_0: crypto_key[31:0] <= reg_write_data;
                KEY_REG_1: crypto_key[63:32] <= reg_write_data;
                KEY_REG_2: crypto_key[95:64] <= reg_write_data;
                KEY_REG_3: crypto_key[127:96] <= reg_write_data;
                KEY_REG_4: crypto_key[159:128] <= reg_write_data;
                KEY_REG_5: crypto_key[191:160] <= reg_write_data;
                KEY_REG_6: crypto_key[223:192] <= reg_write_data;
                KEY_REG_7: crypto_key[255:224] <= reg_write_data;
                CONTROL_REG: control_reg <= reg_write_data;
            endcase
        end
    end

    // VULNERABLE: Key registers are readable!
    always @(*) begin
        case (reg_addr)
            // VULNERABLE: Key can be read back by any software
            KEY_REG_0: reg_read_data = crypto_key[31:0];
            KEY_REG_1: reg_read_data = crypto_key[63:32];
            KEY_REG_2: reg_read_data = crypto_key[95:64];
            KEY_REG_3: reg_read_data = crypto_key[127:96];
            KEY_REG_4: reg_read_data = crypto_key[159:128];
            KEY_REG_5: reg_read_data = crypto_key[191:160];
            KEY_REG_6: reg_read_data = crypto_key[223:192];
            KEY_REG_7: reg_read_data = crypto_key[255:224];
            STATUS_REG: reg_read_data = status_reg;
            CONTROL_REG: reg_read_data = control_reg;
            default: reg_read_data = 32'h0;
        endcase
    end

endmodule

// Vulnerable: RISC-V CSR without proper privilege check
module vulnerable_csr_module (
    input wire clk,
    input wire reset_n,
    input wire [11:0] csr_addr,
    input wire [31:0] csr_write_data,
    input wire csr_write,
    input wire csr_read,
    input wire [1:0] privilege_level,  // 0=U, 1=S, 3=M
    output reg [31:0] csr_read_data,
    output reg illegal_access
);

    // Machine-level CSRs (require M-mode)
    reg [31:0] mstatus;
    reg [31:0] mepc;
    reg [31:0] mcause;
    reg [31:0] mtvec;

    // CSR addresses
    parameter MSTATUS = 12'h300;
    parameter MEPC = 12'h341;
    parameter MCAUSE = 12'h342;
    parameter MTVEC = 12'h305;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            mstatus <= 32'h0;
            mepc <= 32'h0;
            mcause <= 32'h0;
            mtvec <= 32'h0;
            illegal_access <= 1'b0;
        end
        else if (csr_write) begin
            // VULNERABLE: MEPC excluded from privilege check!
            if (csr_addr == MEPC) begin
                // VULNERABLE: Any privilege level can write MEPC
                mepc <= csr_write_data;
            end
            // Other CSRs properly protected
            else if (csr_addr[11:10] == 2'b11 && privilege_level != 2'd3) begin
                illegal_access <= 1'b1;
            end
            else begin
                case (csr_addr)
                    MSTATUS: mstatus <= csr_write_data;
                    MCAUSE: mcause <= csr_write_data;
                    MTVEC: mtvec <= csr_write_data;
                endcase
            end
        end
    end

    // Attack: User mode writes MEPC to redirect exception return
    // to arbitrary code, achieving privilege escalation

endmodule
// Vulnerable: Software accessing unprotected registers

#include <stdint.h>

#define CRYPTO_BASE 0x40000000
#define CRYPTO_KEY_REG_0 (CRYPTO_BASE + 0x00)
#define CRYPTO_KEY_REG_1 (CRYPTO_BASE + 0x04)
// ...

// VULNERABLE: Any process can read the key
void vulnerable_read_key(uint8_t* key_buffer) {
    volatile uint32_t* key_regs = (volatile uint32_t*)CRYPTO_KEY_REG_0;

    // VULNERABLE: No access control check
    // Malicious process can read cryptographic key
    for (int i = 0; i < 8; i++) {
        ((uint32_t*)key_buffer)[i] = key_regs[i];
    }
}

// VULNERABLE: Any process can modify security registers
void vulnerable_modify_security_config(void) {
    volatile uint32_t* security_ctrl = (volatile uint32_t*)0x40001000;

    // VULNERABLE: No privilege check
    *security_ctrl = 0;  // Disable security features
}

Fixed Code

// Fixed: Cryptographic accelerator with protected keys

module secure_crypto_accelerator (
    input wire clk,
    input wire reset_n,
    input wire [7:0] reg_addr,
    input wire [31:0] reg_write_data,
    input wire reg_write,
    input wire reg_read,
    input wire [1:0] privilege_level,  // 0=user, 1=supervisor, 2=hypervisor, 3=secure
    input wire secure_access,           // Hardware-enforced secure access signal
    output reg [31:0] reg_read_data,
    output reg access_denied
);

    // Key storage
    reg [255:0] crypto_key;
    reg key_loaded;

    // Status and control registers
    reg [31:0] status_reg;
    reg [31:0] control_reg;

    // Register addresses
    parameter KEY_REG_0 = 8'h00;
    parameter KEY_REG_1 = 8'h04;
    parameter KEY_REG_2 = 8'h08;
    parameter KEY_REG_3 = 8'h0C;
    parameter KEY_REG_4 = 8'h10;
    parameter KEY_REG_5 = 8'h14;
    parameter KEY_REG_6 = 8'h18;
    parameter KEY_REG_7 = 8'h1C;
    parameter STATUS_REG = 8'h20;
    parameter CONTROL_REG = 8'h24;

    // FIXED: Key addresses are write-only, require secure access
    wire is_key_addr = (reg_addr >= KEY_REG_0) && (reg_addr <= KEY_REG_7);

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            crypto_key <= 256'h0;
            key_loaded <= 1'b0;
            access_denied <= 1'b0;
        end
        else begin
            access_denied <= 1'b0;

            if (reg_write) begin
                // FIXED: Key writes require secure access
                if (is_key_addr) begin
                    if (!secure_access) begin
                        access_denied <= 1'b1;
                    end
                    else begin
                        case (reg_addr)
                            KEY_REG_0: crypto_key[31:0] <= reg_write_data;
                            KEY_REG_1: crypto_key[63:32] <= reg_write_data;
                            KEY_REG_2: crypto_key[95:64] <= reg_write_data;
                            KEY_REG_3: crypto_key[127:96] <= reg_write_data;
                            KEY_REG_4: crypto_key[159:128] <= reg_write_data;
                            KEY_REG_5: crypto_key[191:160] <= reg_write_data;
                            KEY_REG_6: crypto_key[223:192] <= reg_write_data;
                            KEY_REG_7: begin
                                crypto_key[255:224] <= reg_write_data;
                                key_loaded <= 1'b1;
                            end
                        endcase
                    end
                end
                // Control register requires at least supervisor
                else if (reg_addr == CONTROL_REG) begin
                    if (privilege_level < 2'd1) begin
                        access_denied <= 1'b1;
                    end
                    else begin
                        control_reg <= reg_write_data;
                    end
                end
            end
        end
    end

    // FIXED: Key registers are NOT readable
    always @(*) begin
        access_denied = 1'b0;

        if (reg_read) begin
            case (reg_addr)
                // FIXED: Key registers return zero, not actual key
                KEY_REG_0, KEY_REG_1, KEY_REG_2, KEY_REG_3,
                KEY_REG_4, KEY_REG_5, KEY_REG_6, KEY_REG_7: begin
                    reg_read_data = 32'h0;  // Write-only
                    access_denied = 1'b1;   // Flag access attempt
                end
                STATUS_REG: reg_read_data = status_reg;
                CONTROL_REG: begin
                    if (privilege_level < 2'd1) begin
                        reg_read_data = 32'h0;
                        access_denied = 1'b1;
                    end
                    else begin
                        reg_read_data = control_reg;
                    end
                end
                default: reg_read_data = 32'h0;
            endcase
        end
        else begin
            reg_read_data = 32'h0;
        end
    end

endmodule

// Fixed: RISC-V CSR with proper privilege check
module secure_csr_module (
    input wire clk,
    input wire reset_n,
    input wire [11:0] csr_addr,
    input wire [31:0] csr_write_data,
    input wire csr_write,
    input wire csr_read,
    input wire [1:0] privilege_level,
    output reg [31:0] csr_read_data,
    output reg illegal_access
);

    // Machine-level CSRs
    reg [31:0] mstatus;
    reg [31:0] mepc;
    reg [31:0] mcause;
    reg [31:0] mtvec;

    parameter MSTATUS = 12'h300;
    parameter MEPC = 12'h341;
    parameter MCAUSE = 12'h342;
    parameter MTVEC = 12'h305;

    // FIXED: Extract required privilege from CSR address
    wire [1:0] required_privilege = csr_addr[9:8];

    // FIXED: All M-mode CSRs require M-mode privilege
    wire privilege_sufficient = (privilege_level >= required_privilege);

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            mstatus <= 32'h0;
            mepc <= 32'h0;
            mcause <= 32'h0;
            mtvec <= 32'h0;
            illegal_access <= 1'b0;
        end
        else begin
            illegal_access <= 1'b0;

            if (csr_write) begin
                // FIXED: Check privilege for ALL CSRs including MEPC
                if (!privilege_sufficient) begin
                    illegal_access <= 1'b1;
                end
                else begin
                    case (csr_addr)
                        MSTATUS: mstatus <= csr_write_data;
                        MEPC: mepc <= csr_write_data;  // FIXED: Now protected
                        MCAUSE: mcause <= csr_write_data;
                        MTVEC: mtvec <= csr_write_data;
                        default: illegal_access <= 1'b1;
                    endcase
                end
            end

            if (csr_read) begin
                if (!privilege_sufficient) begin
                    illegal_access <= 1'b1;
                    csr_read_data <= 32'h0;
                end
                else begin
                    case (csr_addr)
                        MSTATUS: csr_read_data <= mstatus;
                        MEPC: csr_read_data <= mepc;
                        MCAUSE: csr_read_data <= mcause;
                        MTVEC: csr_read_data <= mtvec;
                        default: csr_read_data <= 32'h0;
                    endcase
                end
            end
        end
    end

endmodule
// Fixed: Software with proper register access control

#include <stdint.h>

#define CRYPTO_BASE 0x40000000

// FIXED: Key loading only through secure API
int secure_load_key(const uint8_t* key, size_t key_len) {
    // FIXED: Check caller privilege
    if (get_current_privilege() < PRIVILEGE_SECURE) {
        log_security_event("Unprivileged key load attempt");
        return -EPERM;
    }

    // FIXED: Validate key length
    if (key_len != 32) {
        return -EINVAL;
    }

    // FIXED: Use secure hardware path
    volatile uint32_t* key_regs = (volatile uint32_t*)(CRYPTO_BASE + 0x00);

    // Disable interrupts during key loading
    uint32_t irq_state = disable_interrupts();

    for (int i = 0; i < 8; i++) {
        key_regs[i] = ((uint32_t*)key)[i];
    }

    // Memory barrier
    __sync_synchronize();

    restore_interrupts(irq_state);

    return 0;
}

// FIXED: Key read returns error
int secure_read_key(uint8_t* key_buffer) {
    // FIXED: Keys are never readable
    log_security_event("Attempt to read cryptographic key");
    return -EPERM;
}

// FIXED: Security configuration requires privilege
int secure_modify_security_config(uint32_t config) {
    if (get_current_privilege() < PRIVILEGE_KERNEL) {
        return -EPERM;
    }

    // Validate configuration value
    if (!is_valid_security_config(config)) {
        return -EINVAL;
    }

    volatile uint32_t* security_ctrl = (volatile uint32_t*)0x40001000;
    *security_ctrl = config;

    return 0;
}

CVE Examples

  • RISC-V processor implementations where MEPC CSR was incorrectly excluded from privilege checks, allowing user-mode privilege escalation
  • Various SoC vulnerabilities where cryptographic key registers were readable by unprivileged software

  • CWE-284: Improper Access Control (parent)
  • CWE-1220: Insufficient Granularity of Access Control (related)
  • CAPEC-680: Exploitation of Improperly Controlled Registers (attack pattern)

References

  1. MITRE Corporation. "CWE-1262: Improper Access Control for Register Interface." https://cwe.mitre.org/data/definitions/1262.html
  2. RISC-V. "Privileged Architecture Specification"
  3. ARM. "TrustZone Security Technology"