Improper Zeroization of Hardware Register

Description

Improper Zeroization of Hardware Register occurs when a hardware product does not properly clear sensitive information from built-in registers when the user of the hardware block changes. Hardware IPs store data in local registers for I/O buffering and intermediate computations. Sensitive data like encryption keys or passwords can persist in these registers. When hardware access changes hands, new users may extract previous sensitive information. Standards like FIPS-140-2 emphasize "zeroization"—the clearing of register contents in cryptographic hardware.

Risk

Improper register zeroization has severe security implications. Cryptographic keys may be exposed to unauthorized users. Passwords may be extracted from registers. Intermediate computation values may leak secrets. Previous user data may be accessible to new users. Security boundaries between users may be violated. Cryptographic hardware may fail security certification. Data remanence may persist even after clearing. Side-channel attacks may exploit residual data.

Solution

Define clear policies for register clearing procedures during architecture and design phases. Specify responsibility (hardware or software) for initiating zeroization. Clear sensitive registers immediately after use. Implement automatic zeroization on context switches. Use secure clearing patterns that overwrite multiple times if needed. Verify zeroization through testing. Consider data remanence characteristics of memory technology. Comply with FIPS-140-2/3 zeroization requirements for cryptographic modules.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Read Application Data - Information disclosure severity depends on what sensitive data is exposed through retained register values. Cryptographic keys and passwords are high-value targets.

Example Code

Vulnerable Code

// Vulnerable: SHA256 wrapper leaves data registers uncleared

module vulnerable_sha256_wrapper (
    input wire clk,
    input wire reset_n,
    input wire [255:0] data_in,
    input wire data_valid,
    input wire [31:0] axi_addr,
    input wire axi_read,
    output reg [255:0] hash_out,
    output reg hash_valid,
    output reg [31:0] axi_rdata
);

    reg [255:0] input_data_reg;
    reg [255:0] intermediate_hash;
    reg hash_complete;

    // SHA256 computation
    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            input_data_reg <= 256'h0;
            intermediate_hash <= 256'h0;
            hash_out <= 256'h0;
            hash_valid <= 1'b0;
        end
        else if (data_valid) begin
            // Store input data for processing
            input_data_reg <= data_in;

            // Compute hash (simplified)
            intermediate_hash <= compute_sha256(data_in);
            hash_out <= intermediate_hash;
            hash_valid <= 1'b1;
        end
        // VULNERABLE: Data remains in registers after hash completion
        // No clearing of input_data_reg or intermediate_hash
    end

    // AXI interface for reading results
    always @(posedge clk) begin
        if (axi_read) begin
            case (axi_addr)
                32'h0000: axi_rdata <= hash_out[31:0];
                32'h0004: axi_rdata <= hash_out[63:32];
                // ...
                // VULNERABLE: Can read back input data!
                32'h0020: axi_rdata <= input_data_reg[31:0];
                32'h0024: axi_rdata <= input_data_reg[63:32];
                // Previous user's sensitive data exposed!
            endcase
        end
    end

endmodule

// Vulnerable: Crypto key stored in register not cleared
module vulnerable_aes_key_register (
    input wire clk,
    input wire reset_n,
    input wire [127:0] key_in,
    input wire key_load,
    input wire [127:0] plaintext,
    input wire encrypt_start,
    output reg [127:0] ciphertext,
    output reg encrypt_done
);

    reg [127:0] key_register;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            key_register <= 128'h0;
            ciphertext <= 128'h0;
            encrypt_done <= 1'b0;
        end
        else begin
            if (key_load) begin
                key_register <= key_in;
            end

            if (encrypt_start) begin
                ciphertext <= aes_encrypt(plaintext, key_register);
                encrypt_done <= 1'b1;
            end

            // VULNERABLE: Key persists in register
            // New user of crypto block can potentially extract key
        end
    end

endmodule
// Vulnerable: Software doesn't zeroize hardware registers

void vulnerable_crypto_operation(uint8_t* key, uint8_t* data, size_t len) {
    // Load key into hardware crypto accelerator
    crypto_hw_load_key(key);

    // Perform encryption
    crypto_hw_encrypt(data, len);

    // VULNERABLE: Key left in hardware registers
    // Next user of crypto hardware could extract it
}

void vulnerable_password_hash(const char* password) {
    // Load password into hash accelerator
    hash_hw_load_data(password, strlen(password));

    // Compute hash
    hash_hw_compute();

    // VULNERABLE: Password data left in hash accelerator registers
}

Fixed Code

// Fixed: SHA256 wrapper with proper zeroization

module secure_sha256_wrapper (
    input wire clk,
    input wire reset_n,
    input wire [255:0] data_in,
    input wire data_valid,
    input wire clear_registers,  // Explicit clear signal
    input wire [31:0] axi_addr,
    input wire axi_read,
    output reg [255:0] hash_out,
    output reg hash_valid,
    output reg [31:0] axi_rdata,
    output reg registers_cleared
);

    reg [255:0] input_data_reg;
    reg [255:0] intermediate_hash;
    reg hash_complete;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            input_data_reg <= 256'h0;
            intermediate_hash <= 256'h0;
            hash_out <= 256'h0;
            hash_valid <= 1'b0;
            registers_cleared <= 1'b1;
        end
        else if (clear_registers) begin
            // FIXED: Explicit zeroization of all sensitive registers
            input_data_reg <= 256'h0;
            intermediate_hash <= 256'h0;
            hash_out <= 256'h0;
            hash_valid <= 1'b0;
            registers_cleared <= 1'b1;
        end
        else if (data_valid) begin
            input_data_reg <= data_in;
            intermediate_hash <= compute_sha256(data_in);
            hash_out <= intermediate_hash;
            hash_valid <= 1'b1;
            registers_cleared <= 1'b0;
        end
    end

    // FIXED: Auto-clear input data after hash completion
    always @(posedge clk) begin
        if (hash_valid && !clear_registers) begin
            // Automatically clear input data after hash is ready
            input_data_reg <= 256'h0;
        end
    end

    // AXI interface - input data not exposed
    always @(posedge clk) begin
        if (axi_read) begin
            case (axi_addr)
                32'h0000: axi_rdata <= hash_out[31:0];
                32'h0004: axi_rdata <= hash_out[63:32];
                // Input data registers NOT readable via AXI
                default: axi_rdata <= 32'h0;
            endcase
        end
    end

endmodule

// Fixed: Crypto key register with automatic zeroization
module secure_aes_key_register (
    input wire clk,
    input wire reset_n,
    input wire [127:0] key_in,
    input wire key_load,
    input wire key_clear,
    input wire [127:0] plaintext,
    input wire encrypt_start,
    input wire context_switch,  // User/context change signal
    output reg [127:0] ciphertext,
    output reg encrypt_done,
    output reg key_cleared
);

    reg [127:0] key_register;
    reg key_valid;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            key_register <= 128'h0;
            ciphertext <= 128'h0;
            encrypt_done <= 1'b0;
            key_valid <= 1'b0;
            key_cleared <= 1'b1;
        end
        else if (key_clear || context_switch) begin
            // FIXED: Clear key on explicit request or context switch
            key_register <= 128'h0;
            key_valid <= 1'b0;
            key_cleared <= 1'b1;
        end
        else begin
            if (key_load) begin
                key_register <= key_in;
                key_valid <= 1'b1;
                key_cleared <= 1'b0;
            end

            if (encrypt_start && key_valid) begin
                ciphertext <= aes_encrypt(plaintext, key_register);
                encrypt_done <= 1'b1;
            end
        end
    end

endmodule

// Fixed: Comprehensive zeroization controller
module zeroization_controller (
    input wire clk,
    input wire reset_n,
    input wire zeroize_request,
    input wire context_change,
    input wire security_violation,
    output reg zeroize_active,
    output reg [31:0] zeroize_address,
    output reg zeroize_write,
    output reg [255:0] zeroize_data,
    output reg zeroization_complete
);

    // State machine for zeroization
    localparam IDLE = 2'b00;
    localparam CLEARING = 2'b01;
    localparam VERIFY = 2'b10;
    localparam DONE = 2'b11;

    reg [1:0] state;
    reg [31:0] clear_count;
    parameter TOTAL_REGISTERS = 32;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            state <= IDLE;
            zeroize_active <= 1'b0;
            zeroization_complete <= 1'b0;
        end
        else begin
            case (state)
                IDLE: begin
                    if (zeroize_request || context_change || security_violation) begin
                        state <= CLEARING;
                        zeroize_active <= 1'b1;
                        clear_count <= 0;
                        zeroization_complete <= 1'b0;
                    end
                end

                CLEARING: begin
                    // Write zeros to all sensitive registers
                    zeroize_address <= clear_count * 4;
                    zeroize_data <= 256'h0;
                    zeroize_write <= 1'b1;

                    if (clear_count >= TOTAL_REGISTERS - 1) begin
                        state <= VERIFY;
                        clear_count <= 0;
                    end else begin
                        clear_count <= clear_count + 1;
                    end
                end

                VERIFY: begin
                    // Verify registers are cleared
                    zeroize_write <= 1'b0;
                    // Verification logic
                    if (clear_count >= TOTAL_REGISTERS - 1) begin
                        state <= DONE;
                    end else begin
                        clear_count <= clear_count + 1;
                    end
                end

                DONE: begin
                    zeroize_active <= 1'b0;
                    zeroization_complete <= 1'b1;
                    state <= IDLE;
                end
            endcase
        end
    end

endmodule
// Fixed: Software with proper hardware zeroization

void secure_crypto_operation(uint8_t* key, uint8_t* data, size_t len) {
    // Load key into hardware crypto accelerator
    crypto_hw_load_key(key);

    // Perform encryption
    crypto_hw_encrypt(data, len);

    // FIXED: Clear key from hardware after use
    crypto_hw_zeroize_key();

    // Verify key was cleared
    if (!crypto_hw_verify_key_cleared()) {
        panic("Key zeroization failed!");
    }
}

void secure_password_hash(const char* password, uint8_t* hash_out) {
    // Load password into hash accelerator
    hash_hw_load_data(password, strlen(password));

    // Compute hash
    hash_hw_compute();

    // Read result
    hash_hw_read_result(hash_out);

    // FIXED: Clear all input data from hardware
    hash_hw_zeroize_input();

    // Verify
    if (!hash_hw_verify_cleared()) {
        panic("Hash input zeroization failed!");
    }
}

// Context switch handler
void context_switch_handler(void) {
    // Before switching to new user/process, clear all crypto state
    crypto_hw_zeroize_all();
    hash_hw_zeroize_all();
    rng_hw_clear_state();

    // Verify all cleared
    if (!verify_all_crypto_hw_cleared()) {
        // Security violation - halt
        security_violation_handler();
    }
}

// FIPS-140-2 compliant zeroization
void fips_zeroize_crypto_module(void) {
    // Multiple overwrite passes for data remanence protection
    for (int pass = 0; pass < 3; pass++) {
        // Pass 1: All zeros
        crypto_hw_overwrite_all(0x00);
        // Pass 2: All ones
        crypto_hw_overwrite_all(0xFF);
        // Pass 3: Random pattern
        crypto_hw_overwrite_all(get_random_pattern());
    }

    // Final zeros
    crypto_hw_overwrite_all(0x00);

    // Verify
    if (!verify_zeroization_complete()) {
        log_security_event("Zeroization verification failed");
    }
}

CVE Examples

Register zeroization failures have been found in cryptographic hardware accelerators where sensitive keys or data remained accessible after operations completed.


  • CWE-226: Sensitive Information in Resource Not Removed Before Reuse (parent)
  • CWE-212: Improper Removal of Sensitive Information Before Storage or Transfer (related)
  • CWE-1258: Exposure of Sensitive System Information Due to Uncleared Debug Information (related)

References

  1. MITRE Corporation. "CWE-1239: Improper Zeroization of Hardware Register." https://cwe.mitre.org/data/definitions/1239.html
  2. FIPS PUB 140-2: Security Requirements for Cryptographic Modules
  3. Gutmann, P. "Data Remanence in Semiconductor Devices" (USENIX Security 2001)
  4. HACK@DAC'21 OpenPiton SoC Examples