Missing Ability to Patch ROM Code

Description

Missing Ability to Patch ROM Code occurs when the absence of patching capability for ROM code leaves systems vulnerable to unfixable security issues discovered post-deployment. Systems using Root-of-Trust (RoT) mechanisms execute immutable ROM code during boot. When vulnerabilities are discovered after shipping, the inability to patch ROM creates a permanent security risk—any security vulnerabilities discovered in the ROM code can never be fixed for the systems that are already in use. This is particularly critical for secure boot implementations and cryptographic operations performed in ROM.

Risk

Missing ROM patching has severe implications. Permanent security vulnerabilities. Unfixable bugs in deployed systems. Root of trust compromised permanently. Cryptographic flaws unrepairable. Secure boot bypass possible. Product recalls may be necessary. Reduced system maintainability. High likelihood of permanent compromise.

Solution

Implement secure patch support allowing ROM code patching on subsequent boots during architecture, design, and implementation phases. Note that hardware initialization and signature verification may remain non-patchable. Support field-programmable patches using hardware fuses for limited post-deployment patching. Include patch loading in early boot sequence. Use signed patches to prevent unauthorized modifications.

Common Consequences

ImpactDetails
MaintainabilityScope: Other

Systems unable to be patched remain in vulnerable states indefinitely, reducing maintainability.

Example Code

Vulnerable Code

// Vulnerable: ROM without patching capability

module vulnerable_boot_rom (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [11:0] rom_addr,
    input  wire        ariane_boot_sel_i,  // Boot selection signal
    output wire [31:0] rom_rdata
);

    // Fixed ROM content - cannot be changed after fabrication
    reg [31:0] rom_linux [0:4095];
    reg [31:0] rom_rdata_linux;

    // ROM initialization (synthesized into mask ROM or OTP)
    initial begin
        $readmemh("linux_bootrom.hex", rom_linux);
    end

    always @(posedge clk) begin
        rom_rdata_linux <= rom_linux[rom_addr];
    end

    // VULNERABLE: Bug - always outputs linux ROM regardless of selection
    // This bug cannot be fixed once the chip is fabricated!
    assign rom_rdata = (ariane_boot_sel_i) ? rom_rdata_linux : rom_rdata_linux;

    // If a security vulnerability is found in rom_linux:
    // - Cannot update the ROM content
    // - Cannot fix the selection logic bug
    // - All deployed devices remain vulnerable

endmodule

// Vulnerable: Secure boot with unpatchable verification
module vulnerable_secure_boot_rom (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [31:0] firmware_addr,
    input  wire [31:0] firmware_data,
    input  wire [2047:0] firmware_signature,
    output reg         boot_authorized,
    output reg         boot_error
);

    // VULNERABLE: Public key embedded in ROM
    // Cannot be changed if key is compromised
    reg [2047:0] rom_public_key;

    initial begin
        // Key is permanently embedded
        rom_public_key = 2048'h<fixed_public_key>;
    end

    // VULNERABLE: Signature verification logic in ROM
    // Any bugs here are permanent
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            boot_authorized <= 1'b0;
            boot_error <= 1'b0;
        end else begin
            // VULNERABLE: If RSA verification has a bug (e.g., padding oracle)
            // it cannot be fixed
            if (rsa_verify(firmware_data, firmware_signature, rom_public_key)) begin
                boot_authorized <= 1'b1;
            end else begin
                boot_error <= 1'b1;
            end
        end
    end

    // If vulnerability discovered (e.g., Bleichenbacher attack):
    // - Cannot update verification algorithm
    // - Cannot revoke compromised key
    // - All devices permanently vulnerable

endmodule
// Vulnerable: Boot ROM code without patch support

// This code is burned into ROM - cannot be changed after manufacturing

#include <stdint.h>

// VULNERABLE: Fixed cryptographic implementation in ROM
// If algorithm has vulnerability, cannot be patched
void rom_sha256(const uint8_t* data, size_t len, uint8_t* hash) {
    // SHA-256 implementation
    // VULNERABLE: If bug found here, unfixable
}

// VULNERABLE: Secure boot verification in ROM
int rom_verify_firmware(const uint8_t* firmware, size_t size,
                        const uint8_t* signature) {
    // VULNERABLE: Fixed public key in ROM
    static const uint8_t rom_public_key[256] = {
        // RSA-2048 public key
        // Cannot be changed if compromised
    };

    uint8_t hash[32];
    rom_sha256(firmware, size, hash);

    // VULNERABLE: RSA verification code in ROM
    // Padding vulnerabilities cannot be fixed
    return rom_rsa_verify(hash, signature, rom_public_key);
}

// VULNERABLE: Main boot sequence in ROM
void rom_boot(void) {
    // 1. Hardware initialization (typically must be in ROM)
    init_hardware();

    // 2. Load firmware from storage
    uint8_t* firmware = load_firmware();
    size_t size = get_firmware_size();
    uint8_t* signature = get_firmware_signature();

    // 3. VULNERABLE: Verification uses potentially buggy ROM code
    if (!rom_verify_firmware(firmware, size, signature)) {
        // Boot failure
        halt();
    }

    // 4. Execute firmware
    // VULNERABLE: No way to apply patches before this point
    jump_to_firmware(firmware);
}

Fixed Code

// Fixed: ROM with patch loading capability

module secure_boot_rom_with_patch (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [11:0] rom_addr,
    input  wire        ariane_boot_sel_i,

    // Patch interface
    input  wire [11:0] patch_addr,
    input  wire [31:0] patch_data,
    input  wire        patch_valid,
    input  wire        patch_enable,       // From fuse or secure register
    input  wire [2047:0] patch_signature,

    output wire [31:0] rom_rdata,
    output reg         patch_error
);

    // Fixed ROM content
    reg [31:0] rom_linux [0:4095];
    reg [31:0] rom_patch [0:4095];  // FIXED: Patchable RAM overlay

    reg [31:0] rom_rdata_linux;
    reg [31:0] rom_rdata_patch;
    reg patch_loaded;

    // FIXED: Fuse bits indicating which addresses are patched
    reg [4095:0] patch_mask;

    // ROM initialization
    initial begin
        $readmemh("linux_bootrom.hex", rom_linux);
        patch_loaded = 1'b0;
        patch_mask = 4096'b0;
    end

    // FIXED: Patch loading with signature verification
    wire patch_sig_valid;

    patch_signature_verifier u_patch_verify (
        .clk(clk),
        .rst_n(rst_n),
        .patch_data(patch_data),
        .patch_signature(patch_signature),
        .signature_valid(patch_sig_valid)
    );

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            patch_loaded <= 1'b0;
            patch_error <= 1'b0;
            patch_mask <= 4096'b0;
        end else if (patch_enable && patch_valid) begin
            // FIXED: Only load signed patches
            if (patch_sig_valid) begin
                rom_patch[patch_addr] <= patch_data;
                patch_mask[patch_addr] <= 1'b1;
                patch_loaded <= 1'b1;
            end else begin
                patch_error <= 1'b1;
            end
        end
    end

    // ROM read
    always @(posedge clk) begin
        rom_rdata_linux <= rom_linux[rom_addr];
        rom_rdata_patch <= rom_patch[rom_addr];
    end

    // FIXED: Output patch data if address is patched, else ROM data
    wire use_patch = patch_loaded && patch_mask[rom_addr];

    // FIXED: Correct boot selection (bug fixed)
    wire [31:0] selected_rom_data;
    assign selected_rom_data = ariane_boot_sel_i ? rom_rdata_patch : rom_rdata_linux;

    // FIXED: Patch overlay
    assign rom_rdata = use_patch ? rom_rdata_patch : selected_rom_data;

endmodule

// Fixed: Fuse-based patching for limited updates
module secure_fuse_patchable_rom (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [11:0] rom_addr,
    output wire [31:0] rom_rdata,

    // Fuse interface
    input  wire [31:0] fuse_patch_0,  // Hardware fuses - one-time programmable
    input  wire [31:0] fuse_patch_1,
    input  wire [31:0] fuse_patch_2,
    input  wire [31:0] fuse_patch_3,
    input  wire [11:0] fuse_patch_addr_0,
    input  wire [11:0] fuse_patch_addr_1,
    input  wire [11:0] fuse_patch_addr_2,
    input  wire [11:0] fuse_patch_addr_3,
    input  wire [3:0]  fuse_patch_valid
);

    reg [31:0] rom_content [0:4095];
    reg [31:0] rom_rdata_reg;

    initial begin
        $readmemh("bootrom.hex", rom_content);
    end

    always @(posedge clk) begin
        // FIXED: Check if address matches a fuse patch
        if (fuse_patch_valid[0] && rom_addr == fuse_patch_addr_0) begin
            rom_rdata_reg <= fuse_patch_0;
        end else if (fuse_patch_valid[1] && rom_addr == fuse_patch_addr_1) begin
            rom_rdata_reg <= fuse_patch_1;
        end else if (fuse_patch_valid[2] && rom_addr == fuse_patch_addr_2) begin
            rom_rdata_reg <= fuse_patch_2;
        end else if (fuse_patch_valid[3] && rom_addr == fuse_patch_addr_3) begin
            rom_rdata_reg <= fuse_patch_3;
        end else begin
            rom_rdata_reg <= rom_content[rom_addr];
        end
    end

    assign rom_rdata = rom_rdata_reg;

    // FIXED: Fuses allow limited field patching
    // Up to 4 instruction patches via OTP fuses

endmodule
// Fixed: Boot ROM with patch loading capability

#include <stdint.h>
#include <stdbool.h>

// Minimal ROM code - just enough to load and verify patches
// Larger boot code is in flash and can be patched

// Fixed ROM public key for patch verification
static const uint8_t ROM_PATCH_KEY[256] = {
    // RSA-2048 key for patch verification only
};

// Patch storage structure
typedef struct {
    uint32_t patch_address;
    uint32_t patch_size;
    uint8_t  patch_data[4096];
    uint8_t  signature[256];
} rom_patch_t;

// FIXED: Verify patch signature before applying
bool rom_verify_patch(const rom_patch_t* patch) {
    uint8_t hash[32];

    // Hash patch metadata and data
    sha256_context ctx;
    sha256_init(&ctx);
    sha256_update(&ctx, &patch->patch_address, sizeof(patch->patch_address));
    sha256_update(&ctx, &patch->patch_size, sizeof(patch->patch_size));
    sha256_update(&ctx, patch->patch_data, patch->patch_size);
    sha256_final(&ctx, hash);

    // Verify signature with ROM key
    return rom_rsa_verify(hash, patch->signature, ROM_PATCH_KEY);
}

// FIXED: Patch RAM overlay
static uint8_t patch_overlay[4096];
static uint32_t patch_base_address;
static uint32_t patch_size;
static bool patch_active = false;

// FIXED: Apply verified patch to RAM overlay
bool rom_apply_patch(const rom_patch_t* patch) {
    // Verify patch first
    if (!rom_verify_patch(patch)) {
        return false;
    }

    // Copy patch to RAM overlay
    memcpy(patch_overlay, patch->patch_data, patch->patch_size);
    patch_base_address = patch->patch_address;
    patch_size = patch->patch_size;
    patch_active = true;

    return true;
}

// FIXED: ROM read function that checks patch overlay
uint32_t rom_read(uint32_t address) {
    // Check if address is in patched region
    if (patch_active &&
        address >= patch_base_address &&
        address < patch_base_address + patch_size) {
        // Return patched data
        return *(uint32_t*)&patch_overlay[address - patch_base_address];
    }

    // Return original ROM data
    return *(uint32_t*)(ROM_BASE + address);
}

// FIXED: Boot sequence with patch support
void rom_boot(void) {
    // 1. Minimal hardware initialization (must be in ROM)
    init_critical_hardware();

    // 2. FIXED: Load and verify patches from secure storage
    rom_patch_t* patch = load_patch_from_storage();
    if (patch != NULL) {
        if (!rom_apply_patch(patch)) {
            // Patch verification failed
            // Continue with unpatched ROM
        }
    }

    // 3. Load secondary bootloader (may be patched)
    uint8_t* bootloader = load_bootloader();
    size_t size = get_bootloader_size();
    uint8_t* signature = get_bootloader_signature();

    // 4. Verify bootloader (using potentially patched verification)
    if (!verify_bootloader(bootloader, size, signature)) {
        halt();
    }

    // 5. Execute bootloader
    jump_to_bootloader(bootloader);
}

// FIXED: Fuse-based emergency patch
void apply_fuse_patches(void) {
    // Read patch data from OTP fuses
    uint32_t fuse_patch_addr = read_fuse(FUSE_PATCH_ADDR);
    uint32_t fuse_patch_data = read_fuse(FUSE_PATCH_DATA);

    if (fuse_patch_addr != 0xFFFFFFFF) {
        // Apply single-instruction fuse patch
        // Limited but available for critical fixes
        apply_instruction_patch(fuse_patch_addr, fuse_patch_data);
    }
}

CVE Examples

  • CVE-2020-0069: MediaTek BootROM vulnerability that couldn't be patched, affecting millions of devices.
  • CVE-2019-14192: U-Boot ROM vulnerabilities requiring hardware replacement.
  • CVE-2021-30900: Apple BootROM (SecureROM) vulnerability in checkm8 exploit.

  • CWE-1329: Reliance on Component That is Not Updateable (parent)
  • CWE-1196: Security Flow Issues (category)
  • CWE-693: Protection Mechanism Failure (related)

References

  1. MITRE Corporation. "CWE-1310: Missing Ability to Patch ROM Code." https://cwe.mitre.org/data/definitions/1310.html
  2. NIST. "Platform Firmware Resiliency Guidelines (SP 800-193)"
  3. OpenTitan. "ROM and ROM Extension Design"