Insufficient Granularity of Access Control
Description
Insufficient Granularity of Access Control occurs when the product's protection mechanism does not account for granularity at the bit level, which allows attackers to bypass the protection mechanism. Various protection schemes such as locks, permissions, and access control lists are implemented in System-on-Chip (SoC) hardware and firmware to control access to registers and other hardware storage elements. These mechanisms may be designed with insufficient granularity—for example, protecting only a register instead of individual bits within it—allowing security bypass.
Risk
Insufficient access control granularity has severe security implications. Attackers may modify sensitive bits within otherwise accessible registers. Security configuration bits may be changed alongside non-sensitive data. Protection mechanisms may be bypassed through partial register access. Privilege escalation may occur through bit-level manipulation. Security policies cannot be properly enforced. Hardware security boundaries may be weakened. Firmware may inadvertently expose sensitive controls. Debug or test bits may be manipulated.
Solution
Implement bit-level access control where security-sensitive bits are present. Separate sensitive bits into dedicated protected registers. Use hardware masks to prevent writes to protected bits. Implement per-bit permission checking in access control logic. Document which bits require protection and why. Verify access control granularity during security reviews. Test that protected bits cannot be modified through any access path. Consider grouping related security bits together. Use write-once locks for critical configuration bits.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Modify Memory - Attackers can modify protected memory at the bit level when access control is not sufficiently granular. High likelihood when sensitive and non-sensitive bits share registers. |
| Integrity | Scope: Integrity Unauthorized Modification - Security configurations may be altered when bit-level protection is missing. |
Example Code
Vulnerable Code
// Vulnerable: Register protection without bit-level granularity
module vulnerable_register_access (
input wire clk,
input wire reset_n,
input wire [31:0] write_data,
input wire write_enable,
input wire [3:0] register_select,
input wire privileged_access,
output reg [31:0] read_data
);
// Control register layout:
// [0] - Feature enable (user accessible)
// [1] - Debug mode (should be protected!)
// [7:2] - Configuration (user accessible)
// [8] - Security bypass (should be protected!)
// [15:9] - More config (user accessible)
// [31:16] - Reserved
reg [31:0] control_register;
reg register_locked;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
control_register <= 32'h0;
register_locked <= 1'b0;
end
else if (write_enable && register_select == 4'b0001) begin
// VULNERABLE: All-or-nothing protection
// Either the entire register is writable or not
if (!register_locked) begin
// User can write ALL bits including security-critical ones
control_register <= write_data;
end
// Once locked, NO bits can be written - too restrictive
end
end
// No bit-level access control
// Sensitive bits mixed with user-accessible bits
endmodule
// Vulnerable: Shared register with mixed sensitivity levels
module vulnerable_mixed_register (
input wire clk,
input wire [31:0] write_data,
input wire write_enable,
input wire user_access,
output reg [31:0] config_register
);
// Mixed register:
// [3:0] - User configuration
// [4] - Privilege escalation bit (CRITICAL!)
// [7:5] - User preferences
// [8] - Disable security checks (CRITICAL!)
// [31:9] - User data
always @(posedge clk) begin
if (write_enable) begin
// VULNERABLE: No distinction between user and critical bits
if (user_access) begin
// User can write everything including critical security bits!
config_register <= write_data;
end
end
end
endmodule
// Vulnerable: Firmware with coarse access control
#define CONFIG_REG_ADDR 0x40001000
// Register bits (mixed sensitivity)
#define CFG_FEATURE_ENABLE (1 << 0) // User accessible
#define CFG_DEBUG_MODE (1 << 1) // Should be protected!
#define CFG_USER_SETTING (0x3F << 2) // User accessible
#define CFG_SECURITY_BYPASS (1 << 8) // Should be protected!
// Vulnerable: All-or-nothing lock
bool register_locked = false;
void write_config_register(uint32_t value) {
// VULNERABLE: Entire register locked or unlocked
// No bit-level granularity
if (!register_locked) {
*(volatile uint32_t*)CONFIG_REG_ADDR = value;
}
}
// User can set security-critical bits!
void user_configure(uint32_t user_settings) {
// User provides full 32-bit value
// Nothing prevents setting CFG_SECURITY_BYPASS or CFG_DEBUG_MODE
write_config_register(user_settings);
}
Fixed Code
// Fixed: Bit-level access control granularity
module secure_register_access (
input wire clk,
input wire reset_n,
input wire [31:0] write_data,
input wire write_enable,
input wire [3:0] register_select,
input wire privileged_access,
output reg [31:0] read_data
);
// Control register with bit-level protection:
// [0] - Feature enable (user accessible)
// [1] - Debug mode (privileged only)
// [7:2] - Configuration (user accessible)
// [8] - Security bypass (privileged only)
// [15:9] - More config (user accessible)
// [31:16] - Reserved (read as zero)
reg [31:0] control_register;
// Bit masks for access control
localparam USER_WRITABLE_MASK = 32'h0000FEFD; // Bits user can write
localparam PRIV_WRITABLE_MASK = 32'h0000FFFF; // Bits privileged can write
localparam PROTECTED_BITS = 32'h00000102; // Debug mode + security bypass
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
control_register <= 32'h0;
end
else if (write_enable && register_select == 4'b0001) begin
if (privileged_access) begin
// Privileged: can write all implemented bits
control_register <= (control_register & ~PRIV_WRITABLE_MASK) |
(write_data & PRIV_WRITABLE_MASK);
end else begin
// User: can only write non-protected bits
control_register <= (control_register & ~USER_WRITABLE_MASK) |
(write_data & USER_WRITABLE_MASK);
// Protected bits [1] and [8] are NOT modified
end
end
end
// Read returns actual value (or could mask protected bits for user)
always @(*) begin
if (privileged_access) begin
read_data = control_register;
end else begin
// Optionally hide protected bits from unprivileged reads
read_data = control_register & USER_WRITABLE_MASK;
end
end
endmodule
// Fixed: Separate registers for different protection levels
module secure_separated_registers (
input wire clk,
input wire reset_n,
input wire [31:0] write_data,
input wire write_enable,
input wire [3:0] register_select,
input wire privileged_access,
output reg [31:0] read_data
);
// Separate registers by sensitivity
reg [31:0] user_config_register; // Address 0x00 - user writable
reg [31:0] security_config_register; // Address 0x04 - privileged only
reg [31:0] debug_config_register; // Address 0x08 - privileged + debug auth
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
user_config_register <= 32'h0;
security_config_register <= 32'h0;
debug_config_register <= 32'h0;
end
else if (write_enable) begin
case (register_select)
4'b0000: begin
// User config - always writable
user_config_register <= write_data;
end
4'b0001: begin
// Security config - privileged only
if (privileged_access) begin
security_config_register <= write_data;
end
// Silently ignore unprivileged writes
end
4'b0010: begin
// Debug config - requires elevated privileges
if (privileged_access && debug_authenticated) begin
debug_config_register <= write_data;
end
end
endcase
end
end
endmodule
// Fixed: Firmware with bit-level access control
#define CONFIG_REG_ADDR 0x40001000
// User-accessible bits
#define CFG_FEATURE_ENABLE (1 << 0)
#define CFG_USER_SETTING (0x3F << 2)
#define CFG_USER_DATA (0x7FFFFF << 9)
// Protected bits (privileged access only)
#define CFG_DEBUG_MODE (1 << 1)
#define CFG_SECURITY_BYPASS (1 << 8)
// Access masks
#define USER_WRITABLE_MASK (CFG_FEATURE_ENABLE | CFG_USER_SETTING | CFG_USER_DATA)
#define PROTECTED_MASK (CFG_DEBUG_MODE | CFG_SECURITY_BYPASS)
void write_config_register_user(uint32_t value) {
// Read current value
uint32_t current = *(volatile uint32_t*)CONFIG_REG_ADDR;
// Preserve protected bits, update only user-writable bits
uint32_t new_value = (current & ~USER_WRITABLE_MASK) |
(value & USER_WRITABLE_MASK);
*(volatile uint32_t*)CONFIG_REG_ADDR = new_value;
}
void write_config_register_privileged(uint32_t value, bool is_privileged) {
if (!is_privileged) {
// Unprivileged: use user path
write_config_register_user(value);
return;
}
// Privileged: can write all bits
*(volatile uint32_t*)CONFIG_REG_ADDR = value;
}
// Safe user configuration function
void user_configure(uint32_t user_settings) {
// Automatically masks out protected bits
write_config_register_user(user_settings);
// Even if user tries to set SECURITY_BYPASS, it won't work
// The bit will be masked out before writing
}
// Validation function
bool validate_access_control_granularity(void) {
uint32_t original = *(volatile uint32_t*)CONFIG_REG_ADDR;
// Try to set protected bits via user interface
write_config_register_user(0xFFFFFFFF);
uint32_t after_user_write = *(volatile uint32_t*)CONFIG_REG_ADDR;
// Verify protected bits unchanged
if ((after_user_write & PROTECTED_MASK) != (original & PROTECTED_MASK)) {
log_error("Access control granularity failure!");
return false;
}
// Restore original
*(volatile uint32_t*)CONFIG_REG_ADDR = original;
return true;
}
CVE Examples
Access control granularity issues have been found in various hardware designs where security-critical bits share registers with user-accessible configuration bits.
Related CWEs
- CWE-284: Improper Access Control (parent)
- CWE-1198: Privilege Separation and Access Control Issues (category member)
- CWE-1222: Insufficient Granularity of Address Regions (related)
References
- MITRE Corporation. "CWE-1220: Insufficient Granularity of Access Control." https://cwe.mitre.org/data/definitions/1220.html
- Hardware Security Best Practices
- SoC Access Control Design Guidelines