Improper Restriction of Write-Once Bit Fields

Description

Improper Restriction of Write-Once Bit Fields occurs when hardware design control register "sticky bits" or write-once bit fields are improperly implemented, such that they can be reprogrammed by software. Register circuits in integrated circuits and hardware IP use write-once protections where "sticky bits" allow initial configuration by boot software while preventing runtime modifications. However, improper implementation—such as allowing writes only when bits are set to "1"—creates "write-1-once" instead of true "write-once" protection, exposing registers to repeated reprogramming.

Risk

Improperly restricted write-once bits have severe security implications. Security configurations can be modified after initial setup. Lock bits may be clearable, defeating their purpose. Boot security settings may be changeable at runtime. Debug interfaces may be re-enabled after being disabled. Fuse protections may be bypassable. Security policies may be weakened post-initialization. Privilege escalation may occur through configuration changes. System integrity may be compromised.

Solution

Implement true write-once semantics that lock on any write regardless of value. Set write-once status unconditionally on first write access. Verify write-once implementation through formal verification. Test that bits cannot be modified after first write. Use hardware state machines to enforce write-once behavior. Document write-once bit behavior clearly. Audit all register implementations for proper sticky bit logic. Consider using non-volatile fuses for critical security settings.

Common Consequences

ImpactDetails
Confidentiality, Integrity, Availability, Access ControlScope: All

System configuration cannot be programmed in a secure way. Attackers can reprogram supposedly protected configuration bits.

Example Code

Vulnerable Code

// Vulnerable: Write-1-once instead of true write-once

module vulnerable_write_once_register (
    input wire clk,
    input wire reset_n,
    input wire [31:0] write_data,
    input wire write_enable,
    output reg [31:0] register_value,
    output reg [31:0] write_once_status
);

    integer i;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            register_value <= 32'h0;
            write_once_status <= 32'h0;
        end
        else if (write_enable) begin
            for (i = 0; i < 32; i = i + 1) begin
                // VULNERABLE: Only locks when writing 1
                // Bits written to 0 are not locked!
                if (write_data[i] == 1'b1) begin
                    if (write_once_status[i] == 1'b0) begin
                        // First write of 1 - set bit and lock
                        register_value[i] <= 1'b1;
                        write_once_status[i] <= 1'b1;
                    end
                    // If already locked, ignore
                end
                else begin
                    // VULNERABLE: Writing 0 always works!
                    // Bit is never locked when written to 0
                    if (write_once_status[i] == 1'b0) begin
                        register_value[i] <= 1'b0;
                        // Bug: Status not updated for 0 writes
                    end
                end
            end
        end
    end

    // Attack scenario:
    // 1. Attacker writes 0x00000000 - all bits set to 0, none locked
    // 2. Legitimate code writes security config
    // 3. Attacker writes 0x00000000 again - clears security bits!

endmodule

// Vulnerable: Lock bit can be cleared
module vulnerable_lock_register (
    input wire clk,
    input wire reset_n,
    input wire [31:0] write_data,
    input wire write_enable,
    output reg [31:0] config_register,
    output reg config_locked
);

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            config_register <= 32'h0;
            config_locked <= 1'b0;
        end
        else if (write_enable) begin
            // VULNERABLE: Lock can be cleared by writing 0
            config_locked <= write_data[31];

            if (!config_locked) begin
                config_register <= write_data[30:0];
            end
        end
    end

    // Attack: Write 0x00000000 to clear lock, then modify config

endmodule
// Vulnerable: Software assuming broken write-once behavior

#define SECURITY_CONFIG_REG 0x40001000
#define LOCK_BIT (1 << 31)

void vulnerable_set_security_config(uint32_t config) {
    // Write security configuration with lock bit
    *(volatile uint32_t*)SECURITY_CONFIG_REG = config | LOCK_BIT;

    // Assumes hardware properly implements write-once
    // But hardware only locks bits written as 1!
}

// Attacker can exploit the vulnerable hardware
void exploit_write_once_bug(void) {
    // Hardware has "write-1-once" bug

    // Step 1: Clear all protection bits
    *(volatile uint32_t*)SECURITY_CONFIG_REG = 0x00000000;

    // Step 2: Security registers are now vulnerable
    // because writing 0 doesn't set the lock

    // Step 3: Wait for legitimate security config...

    // Step 4: Clear again!
    *(volatile uint32_t*)SECURITY_CONFIG_REG = 0x00000000;

    // Security configuration defeated
}

Fixed Code

// Fixed: True write-once implementation

module secure_write_once_register (
    input wire clk,
    input wire reset_n,
    input wire [31:0] write_data,
    input wire write_enable,
    output reg [31:0] register_value,
    output reg [31:0] write_once_status
);

    integer i;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            register_value <= 32'h0;
            write_once_status <= 32'h0;
        end
        else if (write_enable) begin
            for (i = 0; i < 32; i = i + 1) begin
                // FIXED: Lock on ANY write, regardless of value
                if (write_once_status[i] == 1'b0) begin
                    // First write - set value AND lock
                    register_value[i] <= write_data[i];
                    write_once_status[i] <= 1'b1;  // Always lock after first write
                end
                // If already locked, ignore the write entirely
            end
        end
    end

    // Now:
    // 1. Any write locks the bit, whether writing 0 or 1
    // 2. Once locked, bit cannot be modified
    // 3. True write-once semantics

endmodule

// Fixed: Lock bit cannot be cleared once set
module secure_lock_register (
    input wire clk,
    input wire reset_n,
    input wire [31:0] write_data,
    input wire write_enable,
    output reg [31:0] config_register,
    output reg config_locked
);

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            config_register <= 32'h0;
            config_locked <= 1'b0;
        end
        else if (write_enable) begin
            // Lock bit can only transition from 0 to 1
            if (!config_locked && write_data[31]) begin
                config_locked <= 1'b1;  // Can never be cleared
            end
            // Lock bit write of 0 when already locked is ignored

            // Config can only be written when not locked
            if (!config_locked) begin
                config_register <= write_data[30:0];
            end
        end
    end

endmodule

// Fixed: Comprehensive write-once register with verification
module secure_verified_write_once (
    input wire clk,
    input wire reset_n,
    input wire [31:0] write_data,
    input wire write_enable,
    input wire read_enable,
    output reg [31:0] register_value,
    output reg [31:0] write_once_status,
    output reg write_blocked  // Indicates blocked write attempt
);

    integer i;
    reg any_blocked;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            register_value <= 32'h0;
            write_once_status <= 32'h0;
            write_blocked <= 1'b0;
        end
        else if (write_enable) begin
            any_blocked = 1'b0;

            for (i = 0; i < 32; i = i + 1) begin
                if (write_once_status[i] == 1'b0) begin
                    // First write to this bit
                    register_value[i] <= write_data[i];
                    write_once_status[i] <= 1'b1;
                end
                else if (write_data[i] != register_value[i]) begin
                    // Attempted modification of locked bit
                    any_blocked = 1'b1;
                end
            end

            write_blocked <= any_blocked;  // Report blocked writes
        end
        else begin
            write_blocked <= 1'b0;
        end
    end

endmodule
// Fixed: Firmware with proper write-once handling

#define SECURITY_CONFIG_REG 0x40001000
#define WRITE_ONCE_STATUS_REG 0x40001004

void secure_set_security_config(uint32_t config) {
    // Check current write-once status
    uint32_t status = *(volatile uint32_t*)WRITE_ONCE_STATUS_REG;

    if (status != 0) {
        // Some bits already written - verify they match
        uint32_t current = *(volatile uint32_t*)SECURITY_CONFIG_REG;

        if ((current & status) != (config & status)) {
            panic("Cannot modify already-written security config!");
        }
    }

    // Write security configuration
    *(volatile uint32_t*)SECURITY_CONFIG_REG = config;

    // Verify write was successful
    uint32_t readback = *(volatile uint32_t*)SECURITY_CONFIG_REG;
    if (readback != config) {
        panic("Security config write failed!");
    }

    // Verify all bits are now locked
    status = *(volatile uint32_t*)WRITE_ONCE_STATUS_REG;
    if (status != 0xFFFFFFFF) {
        panic("Write-once status incomplete!");
    }
}

// Verification function for write-once behavior
bool verify_write_once_implementation(void) {
    // Test that writing 0 still locks the bit

    // Read current status (should be 0 for unused register)
    uint32_t status_before = *(volatile uint32_t*)WRITE_ONCE_STATUS_REG;

    // Write all zeros
    *(volatile uint32_t*)SECURITY_CONFIG_REG = 0x00000000;

    // Check status - should now be all 1s (all locked)
    uint32_t status_after = *(volatile uint32_t*)WRITE_ONCE_STATUS_REG;

    if (status_after != 0xFFFFFFFF) {
        log_error("Write-once bug: writing 0 doesn't lock bits!");
        return false;
    }

    // Try to change bits
    *(volatile uint32_t*)SECURITY_CONFIG_REG = 0xFFFFFFFF;

    // Should still be all zeros
    uint32_t value = *(volatile uint32_t*)SECURITY_CONFIG_REG;
    if (value != 0x00000000) {
        log_error("Write-once bug: locked bits can be modified!");
        return false;
    }

    return true;
}

CVE Examples

Write-once implementation flaws have been found in various SoC designs where "sticky bits" could be cleared or reprogrammed due to incorrect lock logic.


  • CWE-284: Improper Access Control (parent)
  • CWE-1199: General Circuit and Logic Design Concerns (category member)
  • CWE-1231: Improper Prevention of Lock Bit Modification (related)

References

  1. MITRE Corporation. "CWE-1224: Improper Restriction of Write-Once Bit Fields." https://cwe.mitre.org/data/definitions/1224.html
  2. CAPEC-680: Exploitation of Improperly Controlled Registers
  3. Hardware Security Register Design Guidelines