Missing Immutable Root of Trust in Hardware
Description
Missing Immutable Root of Trust in Hardware occurs when a missing immutable root of trust in the hardware results in the ability to bypass secure boot or execute untrusted or adversarial boot code. The weakness involves SoCs that implement secure boot through code verification but fail to protect the foundational trust elements. Critical components including boot code, cryptographic keys, and system settings stored in fuses must remain locked after initial provisioning. When these root-of-trust elements remain mutable, attackers can modify them to execute unauthorized code.
Risk
Missing immutable root of trust has severe implications. Secure boot bypass enabled. Untrusted boot code execution. Cryptographic key compromise. System settings tamperable. Firmware verification defeated. Privilege escalation enabled. Memory modification possible. Persistent malware installation. Supply chain attacks. High likelihood when root of trust elements are not properly protected.
Solution
Designate root of trust storage in non-programmable memory during architecture and design phase. Verify root of trust memory prevents further writes during testing implementation phase. Use OTP (One-Time Programmable) fuses for critical security parameters. Implement hardware locks that cannot be unlocked after provisioning. Store boot code in ROM or protected flash with hardware write protection.
Common Consequences
| Impact | Details |
|---|---|
| Authentication | Scope: Authentication Secure boot verification bypassed through modified root of trust. |
| Authorization | Scope: Authorization Privilege escalation and unauthorized code execution enabled. |
| Integrity | Scope: Integrity Memory modification possible through compromised boot chain. |
Example Code
Vulnerable Code
// Vulnerable: Root of trust stored in mutable memory
module vulnerable_root_of_trust (
input wire clk,
input wire rst_n,
// Boot key storage (VULNERABLE: writable)
input wire [255:0] key_write_data,
input wire key_write_en,
output reg [255:0] boot_key,
// Boot code hash (VULNERABLE: writable)
input wire [255:0] hash_write_data,
input wire hash_write_en,
output reg [255:0] expected_boot_hash,
// Security settings (VULNERABLE: writable)
input wire [31:0] settings_write_data,
input wire settings_write_en,
output reg [31:0] security_settings,
// Boot verification
input wire [255:0] actual_boot_hash,
input wire verify_boot,
output reg boot_verified
);
// VULNERABLE: Root of trust values stored in regular registers
// Can be modified after initial provisioning
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
boot_key <= 256'h0;
expected_boot_hash <= 256'h0;
security_settings <= 32'h0;
end else begin
// VULNERABLE: Key can be overwritten anytime
if (key_write_en) begin
boot_key <= key_write_data;
end
// VULNERABLE: Expected hash can be changed
if (hash_write_en) begin
expected_boot_hash <= hash_write_data;
end
// VULNERABLE: Security settings modifiable
if (settings_write_en) begin
security_settings <= settings_write_data;
end
end
end
// Boot verification uses potentially compromised values
always @(posedge clk) begin
if (verify_boot) begin
// VULNERABLE: Compares against attacker-modifiable hash
boot_verified <= (actual_boot_hash == expected_boot_hash);
end
end
// Attack:
// 1. Write malicious boot code to flash
// 2. Update expected_boot_hash to match malicious code
// 3. Secure boot passes, malicious code executes
endmodule
// Vulnerable: Fuse controller without lock mechanism
module vulnerable_fuse_controller (
input wire clk,
input wire rst_n,
// Fuse programming interface
input wire [7:0] fuse_addr,
input wire [31:0] fuse_data,
input wire fuse_program,
// Fuse read interface
output reg [31:0] fuse_read_data
);
// Fuse storage (simulated)
reg [31:0] fuses [0:255];
// VULNERABLE: No lock mechanism
// Fuses can be reprogrammed after initial provisioning
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// Initialize fuses
end else if (fuse_program) begin
// VULNERABLE: Always allows programming
fuses[fuse_addr] <= fuse_data;
end
end
always @(*) begin
fuse_read_data = fuses[fuse_addr];
end
// Attack: Reprogram security fuses to bypass protection
endmodule
// Vulnerable: Software using mutable trust store
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
// VULNERABLE: Root of trust in regular RAM
typedef struct {
uint8_t root_key[32];
uint8_t expected_hash[32];
uint32_t security_flags;
} root_of_trust_t;
// VULNERABLE: Stored in regular memory
static root_of_trust_t rot_store;
// VULNERABLE: Can be modified anytime
void vulnerable_set_root_key(const uint8_t* key) {
memcpy(rot_store.root_key, key, 32);
}
void vulnerable_set_expected_hash(const uint8_t* hash) {
memcpy(rot_store.expected_hash, hash, 32);
}
bool vulnerable_verify_boot(const uint8_t* firmware, size_t size) {
uint8_t computed_hash[32];
sha256(firmware, size, computed_hash);
// VULNERABLE: Compares against modifiable hash
return memcmp(computed_hash, rot_store.expected_hash, 32) == 0;
}
// Attack: Modify rot_store.expected_hash before verification
// Any firmware will pass verification
Fixed Code
// Fixed: Immutable root of trust with hardware protection
module secure_root_of_trust (
input wire clk,
input wire rst_n,
// Boot key storage (OTP - One-Time Programmable)
input wire [255:0] key_write_data,
input wire key_write_en,
input wire provisioning_mode, // FIXED: Only during provisioning
output wire [255:0] boot_key,
// Boot code hash (OTP)
input wire [255:0] hash_write_data,
input wire hash_write_en,
output wire [255:0] expected_boot_hash,
// Security settings (OTP with lock)
input wire [31:0] settings_write_data,
input wire settings_write_en,
output wire [31:0] security_settings,
// Lock status
output reg rot_locked,
output reg write_denied,
// Boot verification
input wire [255:0] actual_boot_hash,
input wire verify_boot,
output reg boot_verified
);
// FIXED: OTP fuse storage (simulated as registers that can only be set, not cleared)
reg [255:0] key_fuses;
reg [255:0] hash_fuses;
reg [31:0] settings_fuses;
reg lock_fuse; // FIXED: Lock bit
// FIXED: Key provisioning only when unlocked and in provisioning mode
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
key_fuses <= 256'h0;
write_denied <= 1'b0;
end else if (key_write_en) begin
write_denied <= 1'b0;
// FIXED: Only allow write in provisioning mode and before lock
if (provisioning_mode && !lock_fuse) begin
// FIXED: OTP behavior - can only set bits, not clear
key_fuses <= key_fuses | key_write_data;
end else begin
write_denied <= 1'b1;
end
end
end
// FIXED: Hash provisioning with same protection
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
hash_fuses <= 256'h0;
end else if (hash_write_en && provisioning_mode && !lock_fuse) begin
hash_fuses <= hash_fuses | hash_write_data;
end
end
// FIXED: Settings provisioning
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
settings_fuses <= 32'h0;
end else if (settings_write_en && provisioning_mode && !lock_fuse) begin
settings_fuses <= settings_fuses | settings_write_data;
end
end
// FIXED: Lock fuse - once set, cannot be cleared
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
lock_fuse <= 1'b0;
end else if (settings_write_en && settings_write_data[31] && provisioning_mode) begin
// FIXED: Lock bit can only be set, never cleared
lock_fuse <= 1'b1;
end
end
// FIXED: Output from immutable fuses
assign boot_key = key_fuses;
assign expected_boot_hash = hash_fuses;
assign security_settings = settings_fuses;
assign rot_locked = lock_fuse;
// FIXED: Boot verification against immutable hash
always @(posedge clk) begin
if (verify_boot) begin
// Compares against hardware-protected hash
boot_verified <= (actual_boot_hash == hash_fuses);
end
end
endmodule
// Fixed: Secure fuse controller with lock
module secure_fuse_controller (
input wire clk,
input wire rst_n,
// Fuse programming interface
input wire [7:0] fuse_addr,
input wire [31:0] fuse_data,
input wire fuse_program,
input wire provisioning_mode,
// Fuse read interface
output reg [31:0] fuse_read_data,
output reg program_denied,
// Lock status
output wire fuses_locked
);
reg [31:0] fuses [0:255];
reg [255:0] fuse_programmed; // Track which fuses have been programmed
reg global_lock;
// FIXED: Lock address
localparam LOCK_FUSE_ADDR = 8'hFF;
// FIXED: Fuse programming with restrictions
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
global_lock <= 1'b0;
program_denied <= 1'b0;
end else if (fuse_program) begin
program_denied <= 1'b0;
// FIXED: Check if locked or not in provisioning
if (global_lock || !provisioning_mode) begin
program_denied <= 1'b1;
end
// FIXED: Check if already programmed (OTP)
else if (fuse_programmed[fuse_addr]) begin
program_denied <= 1'b1;
end
else begin
// FIXED: Program fuse (OTP - only once)
fuses[fuse_addr] <= fuse_data;
fuse_programmed[fuse_addr] <= 1'b1;
// FIXED: Check for lock fuse
if (fuse_addr == LOCK_FUSE_ADDR && fuse_data[0]) begin
global_lock <= 1'b1;
end
end
end
end
assign fuses_locked = global_lock;
always @(*) begin
fuse_read_data = fuses[fuse_addr];
end
endmodule
// Fixed: Immutable root of trust implementation
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
// FIXED: Hardware addresses for OTP fuses
#define OTP_ROOT_KEY_ADDR 0x10000000
#define OTP_HASH_ADDR 0x10000020
#define OTP_SETTINGS_ADDR 0x10000040
#define OTP_LOCK_ADDR 0x100000FF
// FIXED: Read from hardware OTP (cannot be modified by software)
static inline void read_otp(uint32_t addr, uint8_t* data, size_t len) {
volatile uint8_t* otp = (volatile uint8_t*)addr;
for (size_t i = 0; i < len; i++) {
data[i] = otp[i];
}
}
// FIXED: Check if OTP is locked
static inline bool is_otp_locked(void) {
volatile uint32_t* lock = (volatile uint32_t*)OTP_LOCK_ADDR;
return (*lock & 1) != 0;
}
// FIXED: Secure boot verification using hardware OTP
bool secure_verify_boot(const uint8_t* firmware, size_t size) {
uint8_t expected_hash[32];
uint8_t computed_hash[32];
// FIXED: Read expected hash from hardware OTP (immutable)
read_otp(OTP_HASH_ADDR, expected_hash, 32);
// Compute hash of firmware
sha256(firmware, size, computed_hash);
// FIXED: Compare against hardware-protected hash
// Software cannot modify expected_hash
return secure_compare(computed_hash, expected_hash, 32) == 0;
}
// FIXED: Provisioning only allowed once
bool provision_root_of_trust(const uint8_t* key, const uint8_t* hash) {
// FIXED: Check if already locked
if (is_otp_locked()) {
return false; // Cannot provision - already locked
}
// FIXED: Program OTP (hardware ensures one-time-programmable)
// This requires special provisioning mode signal from hardware
if (!program_otp(OTP_ROOT_KEY_ADDR, key, 32)) {
return false;
}
if (!program_otp(OTP_HASH_ADDR, hash, 32)) {
return false;
}
// FIXED: Lock OTP after provisioning
uint32_t lock_value = 1;
if (!program_otp(OTP_LOCK_ADDR, (uint8_t*)&lock_value, 4)) {
return false;
}
return true;
}
CVE Examples
- CVE-2020-0069: MediaTek bootloader allowed bypass of secure boot due to mutable root of trust.
- CVE-2019-6260: BMC firmware verification bypass through modifiable trust store.
Related CWEs
- CWE-693: Protection Mechanism Failure (parent)
- CWE-1196: Security Flow Issues (category)
- CWE-1310: Missing Ability to Patch ROM Code (related - opposite problem)
- CAPEC-679: Exploitation of Improperly Configured Memory Protections
- CAPEC-68: Subverting Code-Signing Facilities
References
- MITRE Corporation. "CWE-1326: Missing Immutable Root of Trust in Hardware." https://cwe.mitre.org/data/definitions/1326.html
- NIST. "Platform Firmware Resiliency Guidelines"
- TCG. "Trusted Platform Module Specification"