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

ImpactDetails
AuthenticationScope: Authentication

Secure boot verification bypassed through modified root of trust.
AuthorizationScope: Authorization

Privilege escalation and unauthorized code execution enabled.
IntegrityScope: 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.

  • 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

  1. MITRE Corporation. "CWE-1326: Missing Immutable Root of Trust in Hardware." https://cwe.mitre.org/data/definitions/1326.html
  2. NIST. "Platform Firmware Resiliency Guidelines"
  3. TCG. "Trusted Platform Module Specification"