Improper Prevention of Lock Bit Modification

Description

Improper Prevention of Lock Bit Modification occurs when a system employs a trusted lock bit to restrict access to registers or resources, but fails to prevent modification of the lock bit itself after initialization. Hardware devices typically lock configuration controls after power-on using a lock bit mechanism that disables further writes to protected registers. However, design or implementation flaws may allow attackers to modify or clear the lock bit post-initialization, potentially unlocking protected features and compromising system security.

Risk

Lock bit modification vulnerabilities have severe security implications. Protected configurations can be unlocked and modified. Security settings may be disabled after boot. Debug interfaces may be re-enabled. Secure boot protections may be bypassed. Fuse-based protections may be circumvented. Firmware integrity may be compromised. Privilege escalation may be possible. Hardware security boundaries may be violated.

Solution

Protect lock bits from modification once set. Ensure lock bits cannot be cleared by software after initialization. Use hardware-enforced write-once lock mechanisms. Verify all reset paths preserve lock bit state. Remove peripheral reset signals from lock bit reset conditions. Test lock bit persistence across all system states. Implement redundant lock mechanisms. Audit lock bit implementation for bypass paths. Consider using irreversible fuses for critical locks.

Common Consequences

ImpactDetails
Access ControlScope: Access Control

Modify Memory - Protected registers can be altered even when the lock is supposedly active, compromising access control protections.
IntegrityScope: Integrity

Bypass Protection Mechanism - Security configurations may be unlocked and modified after supposedly being secured.

Example Code

Vulnerable Code

// Vulnerable: Lock bit can be cleared through peripheral reset

module vulnerable_register_lock (
    input wire clk,
    input wire rst_ni,          // Global reset
    input wire jtag_unlock,     // JTAG unlock signal
    input wire rst_peripheral,  // Peripheral-specific reset
    input wire [31:0] write_data,
    input wire write_enable,
    input wire lock_write,
    output reg [31:0] protected_register,
    output reg register_locked
);

    always @(posedge clk or negedge rst_ni) begin
        // VULNERABLE: Lock reset includes peripheral reset
        // Attacker can trigger peripheral reset to clear lock!
        if (~(rst_ni && ~jtag_unlock && ~rst_peripheral)) begin
            register_locked <= 1'b0;  // Lock cleared!
            protected_register <= 32'h0;
        end
        else begin
            if (lock_write && !register_locked) begin
                register_locked <= 1'b1;
            end

            if (write_enable && !register_locked) begin
                protected_register <= write_data;
            end
        end
    end

endmodule

// Vulnerable: Lock bit stored in regular register (can be written)
module vulnerable_soft_lock (
    input wire clk,
    input wire reset_n,
    input wire [31:0] write_data,
    input wire write_enable,
    input wire [7:0] register_select,
    output reg [31:0] config_register,
    output reg [31:0] lock_register  // VULNERABLE: Lock is writable!
);

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            config_register <= 32'h0;
            lock_register <= 32'h0;
        end
        else if (write_enable) begin
            case (register_select)
                8'h00: begin
                    // Config register - check lock
                    if (lock_register[0] == 1'b0) begin
                        config_register <= write_data;
                    end
                end
                8'h01: begin
                    // VULNERABLE: Lock register is writable!
                    // Attacker can write 0 to clear lock
                    lock_register <= write_data;
                end
            endcase
        end
    end

endmodule

// Vulnerable: Incomplete lock coverage
module vulnerable_incomplete_lock (
    input wire clk,
    input wire reset_n,
    input wire [31:0] write_data,
    input wire write_enable,
    output reg [31:0] temp_register,     // Locked
    output reg [31:0] calibration_reg,   // Locked
    output reg [31:0] shutdown_response, // VULNERABLE: Not locked!
    output reg register_locked
);

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            temp_register <= 32'h0;
            calibration_reg <= 32'h0;
            shutdown_response <= 32'h0;
            register_locked <= 1'b0;
        end
        else begin
            // Lock protects temp and calibration
            if (!register_locked) begin
                temp_register <= write_data;
                calibration_reg <= write_data;
            end

            // VULNERABLE: shutdown_response not protected by lock!
            // Attacker can modify safety-critical response
            shutdown_response <= write_data;
        end
    end

endmodule
// Vulnerable: Firmware with clearable lock bit

#define CONFIG_REG_ADDR  0x40001000
#define LOCK_REG_ADDR    0x40001004

void vulnerable_lock_config(void) {
    // Configure the register
    *(volatile uint32_t*)CONFIG_REG_ADDR = SECURE_CONFIG_VALUE;

    // Set lock bit
    *(volatile uint32_t*)LOCK_REG_ADDR = 1;

    // VULNERABLE: Lock can be cleared!
}

void attacker_clear_lock(void) {
    // Hardware doesn't prevent clearing the lock
    *(volatile uint32_t*)LOCK_REG_ADDR = 0;

    // Now config register is writable again
    *(volatile uint32_t*)CONFIG_REG_ADDR = MALICIOUS_CONFIG;
}

// Vulnerable: Using peripheral reset to clear lock
void attacker_reset_to_clear_lock(void) {
    // Trigger peripheral reset
    trigger_peripheral_reset(PERIPHERAL_ID);

    // Lock bit is cleared by reset
    // Now can modify supposedly locked registers
}

Fixed Code

// Fixed: Lock bit protected from modification

module secure_register_lock (
    input wire clk,
    input wire rst_ni,          // Global reset only
    input wire jtag_unlock,     // JTAG unlock (with proper auth)
    input wire rst_peripheral,  // Peripheral reset - NOT used for lock!
    input wire [31:0] write_data,
    input wire write_enable,
    input wire lock_write,
    output reg [31:0] protected_register,
    output reg register_locked
);

    always @(posedge clk or negedge rst_ni) begin
        // FIXED: Lock only cleared by global reset or authenticated JTAG
        // Peripheral reset does NOT affect lock
        if (~rst_ni) begin
            register_locked <= 1'b0;
            protected_register <= 32'h0;
        end
        else if (jtag_unlock) begin
            // Only authenticated JTAG can clear lock
            register_locked <= 1'b0;
        end
        else begin
            // Lock can only transition from 0 to 1
            if (lock_write && !register_locked) begin
                register_locked <= 1'b1;
            end
            // Lock cannot be cleared by software

            // Protected register only writable when unlocked
            if (write_enable && !register_locked) begin
                protected_register <= write_data;
            end
        end
    end

    // Peripheral reset only affects data, not lock
    always @(posedge clk) begin
        if (rst_peripheral && register_locked) begin
            // Peripheral reset with lock active: data preserved
            // Lock remains set
        end
    end

endmodule

// Fixed: Write-once lock bit implementation
module secure_write_once_lock (
    input wire clk,
    input wire reset_n,
    input wire [31:0] write_data,
    input wire write_enable,
    input wire [7:0] register_select,
    output reg [31:0] config_register,
    output reg lock_set  // Write-once lock
);

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            config_register <= 32'h0;
            lock_set <= 1'b0;
        end
        else if (write_enable) begin
            case (register_select)
                8'h00: begin
                    // Config register - check lock
                    if (!lock_set) begin
                        config_register <= write_data;
                    end
                end
                8'h01: begin
                    // FIXED: Lock can only be SET, never cleared
                    if (!lock_set && write_data[0]) begin
                        lock_set <= 1'b1;
                    end
                    // Writes to clear lock are ignored
                end
            endcase
        end
    end

endmodule

// Fixed: Complete lock coverage for all sensitive registers
module secure_complete_lock (
    input wire clk,
    input wire reset_n,
    input wire [31:0] write_data,
    input wire write_enable,
    input wire [7:0] register_select,
    input wire set_lock,
    output reg [31:0] temp_register,
    output reg [31:0] calibration_reg,
    output reg [31:0] shutdown_response,
    output reg register_locked
);

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            temp_register <= 32'h0;
            calibration_reg <= 32'h0;
            shutdown_response <= 32'h0;
            register_locked <= 1'b0;
        end
        else begin
            // Lock can only be set, not cleared
            if (set_lock && !register_locked) begin
                register_locked <= 1'b1;
            end

            if (write_enable && !register_locked) begin
                case (register_select)
                    8'h00: temp_register <= write_data;
                    8'h01: calibration_reg <= write_data;
                    8'h02: shutdown_response <= write_data;  // Now protected!
                endcase
            end
            // All writes blocked when locked
        end
    end

endmodule

// Fixed: Hardware-enforced permanent lock
module secure_permanent_lock #(
    parameter LOCK_ON_BOOT = 1  // Lock immediately after boot config
) (
    input wire clk,
    input wire reset_n,
    input wire boot_complete,
    input wire [31:0] write_data,
    input wire write_enable,
    output reg [31:0] protected_register,
    output reg permanently_locked
);

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            protected_register <= 32'h0;
            permanently_locked <= 1'b0;
        end
        else begin
            // Auto-lock when boot completes
            if (LOCK_ON_BOOT && boot_complete && !permanently_locked) begin
                permanently_locked <= 1'b1;
            end

            // Only writable before permanent lock
            if (write_enable && !permanently_locked) begin
                protected_register <= write_data;
            end
        end
    end

    // Cannot be unlocked by any means except full reset

endmodule
// Fixed: Firmware with proper lock bit handling

#define CONFIG_REG_ADDR  0x40001000
#define LOCK_REG_ADDR    0x40001004
#define LOCK_STATUS_ADDR 0x40001008

void secure_lock_config(void) {
    // Configure the register
    *(volatile uint32_t*)CONFIG_REG_ADDR = SECURE_CONFIG_VALUE;

    // Set write-once lock bit
    *(volatile uint32_t*)LOCK_REG_ADDR = 1;

    // Verify lock is set
    if (!(*(volatile uint32_t*)LOCK_STATUS_ADDR & 0x1)) {
        panic("Failed to set config lock!");
    }

    // Try to clear (should fail)
    *(volatile uint32_t*)LOCK_REG_ADDR = 0;

    // Verify lock still set
    if (!(*(volatile uint32_t*)LOCK_STATUS_ADDR & 0x1)) {
        panic("Lock bit was cleared - hardware vulnerability!");
    }

    // Try to modify config (should fail)
    uint32_t original = *(volatile uint32_t*)CONFIG_REG_ADDR;
    *(volatile uint32_t*)CONFIG_REG_ADDR = 0xFFFFFFFF;

    if (*(volatile uint32_t*)CONFIG_REG_ADDR != original) {
        panic("Locked register was modified - hardware vulnerability!");
    }
}

// Verification function
bool verify_lock_bit_implementation(void) {
    bool passed = true;

    // Test 1: Lock bit can be set
    *(volatile uint32_t*)LOCK_REG_ADDR = 1;
    if (!(*(volatile uint32_t*)LOCK_STATUS_ADDR & 0x1)) {
        log_error("Lock bit cannot be set");
        passed = false;
    }

    // Test 2: Lock bit cannot be cleared by writing 0
    *(volatile uint32_t*)LOCK_REG_ADDR = 0;
    if (!(*(volatile uint32_t*)LOCK_STATUS_ADDR & 0x1)) {
        log_error("Lock bit cleared by writing 0");
        passed = false;
    }

    // Test 3: Protected register cannot be modified when locked
    uint32_t before = *(volatile uint32_t*)CONFIG_REG_ADDR;
    *(volatile uint32_t*)CONFIG_REG_ADDR = ~before;
    uint32_t after = *(volatile uint32_t*)CONFIG_REG_ADDR;
    if (after != before) {
        log_error("Locked register modified");
        passed = false;
    }

    // Test 4: Peripheral reset doesn't clear lock
    trigger_peripheral_reset(PERIPHERAL_ID);
    if (!(*(volatile uint32_t*)LOCK_STATUS_ADDR & 0x1)) {
        log_error("Peripheral reset cleared lock");
        passed = false;
    }

    return passed;
}

CVE Examples

  • CVE-2017-6283: Chip reset clears critical RSA function read/write lock permissions
  • OpenPiton SoC vulnerabilities from HACK@DAC'21 demonstrated lock bit bypass through peripheral reset

  • CWE-284: Improper Access Control (parent)
  • CWE-1199: General Circuit and Logic Design Concerns (category member)
  • CWE-1224: Improper Restriction of Write-Once Bit Fields (related)
  • CWE-1233: Security-Sensitive Hardware Controls with Missing Lock Bit Protection (related)

References

  1. MITRE Corporation. "CWE-1231: Improper Prevention of Lock Bit Modification." https://cwe.mitre.org/data/definitions/1231.html
  2. Hardware Security Lock Bit Design Guidelines
  3. OpenPiton SoC Security Analysis