Hardware Allows Activation of Test or Debug Logic at Runtime

Description

Hardware Allows Activation of Test or Debug Logic at Runtime occurs when during runtime, the hardware allows for test or debug logic (feature) to be activated, which allows for changing the state of the hardware. This activation capability creates security risks by enabling adversaries to modify system behavior and access sensitive information. Examples include read/write access to system data through test modes, data modification via error injection during bus operations, and unauthorized access to system secrets.

Risk

Runtime test/debug activation has severe implications. Memory modification possible. Unauthorized reads enabled. Denial of service through instability. Unauthorized code execution. Privilege escalation. Protection mechanism bypass. Execution logic altered. Performance degraded. Reliability issues. Hardware state compromise. High likelihood when test modes remain accessible.

Solution

Insert restrictions on when the hardware's test or debug features can be activated. Prevent privileged mode access during normal operations. Limit test/debug activation to specific windows such as boot or configuration stages. Disable such features completely during runtime operations. Use hardware fuses to permanently disable test features in production. Require authentication for debug activation.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

System secrets readable through debug interfaces.
IntegrityScope: Integrity

Memory and execution state modifiable via test modes.
AvailabilityScope: Availability

Denial of service through crashes, instability, or resource exhaustion.

Example Code

Vulnerable Code

// Vulnerable: Test mode activatable at runtime

module vulnerable_test_controller (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        test_mode_enable,   // Software-controllable!
    input  wire [31:0] test_addr,
    input  wire [31:0] test_wdata,
    input  wire        test_write,
    input  wire        test_read,

    // System interfaces
    output reg  [31:0] sys_addr,
    output reg  [31:0] sys_wdata,
    output reg         sys_write,
    output reg         sys_read,
    output reg  [31:0] sys_rdata,

    // Internal debug access
    output reg         debug_mem_access,
    output reg         error_inject_enable
);

    // VULNERABLE: Test mode can be enabled at any time
    // No restrictions on when or who can enable it

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            debug_mem_access <= 1'b0;
            error_inject_enable <= 1'b0;
        end else begin
            // VULNERABLE: Test mode grants special capabilities
            if (test_mode_enable) begin
                // VULNERABLE: Direct memory access bypassing protection
                sys_addr <= test_addr;
                sys_wdata <= test_wdata;
                sys_write <= test_write;
                sys_read <= test_read;

                // VULNERABLE: Debug features enabled at runtime
                debug_mem_access <= 1'b1;
                error_inject_enable <= 1'b1;
            end else begin
                debug_mem_access <= 1'b0;
                error_inject_enable <= 1'b0;
            end
        end
    end

    // Attack:
    // 1. Malicious software sets test_mode_enable = 1
    // 2. Debug memory access enabled
    // 3. Attacker reads/writes protected memory
    // 4. Error injection corrupts system state

endmodule

// Vulnerable: Debug logic accessible during normal operation
module vulnerable_cpu_debug (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        debug_request,     // VULNERABLE: Always available
    input  wire [31:0] debug_addr,
    input  wire [31:0] debug_data,
    input  wire        debug_write,

    // CPU internals (should be protected)
    output reg  [31:0] pc_override,
    output reg         halt_cpu,
    output reg         single_step,
    output reg  [31:0] register_write_data,
    output reg  [4:0]  register_write_addr,
    output reg         register_write_en
);

    // VULNERABLE: No lifecycle state check
    // Debug accessible even after secure boot completes

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            halt_cpu <= 1'b0;
            single_step <= 1'b0;
            register_write_en <= 1'b0;
        end else if (debug_request) begin
            // VULNERABLE: Debug commands processed at runtime

            case (debug_addr[7:0])
                8'h00: begin
                    // Halt CPU
                    halt_cpu <= debug_data[0];
                end
                8'h04: begin
                    // Override program counter
                    pc_override <= debug_data;  // VULNERABLE!
                end
                8'h08: begin
                    // Write CPU register
                    register_write_addr <= debug_data[4:0];
                    register_write_data <= debug_data;
                    register_write_en <= 1'b1;  // VULNERABLE!
                end
                8'h0C: begin
                    // Enable single-step mode
                    single_step <= debug_data[0];
                end
            endcase
        end
    end

    // VULNERABLE: No authentication required
    // VULNERABLE: No lifecycle check (production vs development)

endmodule

// Vulnerable: BIST accessible at runtime
module vulnerable_bist_controller (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        bist_start,        // Software-triggerable
    input  wire [7:0]  bist_pattern,
    output reg         bist_running,
    output reg         bist_complete,
    output reg         bist_pass
);

    reg [31:0] memory [0:1023];
    reg [9:0] test_addr;

    // VULNERABLE: BIST can be started at any time
    // This can corrupt memory during normal operation!

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            bist_running <= 1'b0;
            bist_complete <= 1'b0;
        end else if (bist_start && !bist_running) begin
            // VULNERABLE: BIST destroys memory contents!
            bist_running <= 1'b1;
            test_addr <= 10'b0;
        end else if (bist_running) begin
            // VULNERABLE: Overwrites live memory with test patterns
            memory[test_addr] <= {4{bist_pattern}};
            test_addr <= test_addr + 1;

            if (test_addr == 10'h3FF) begin
                bist_complete <= 1'b1;
                bist_running <= 1'b0;
            end
        end
    end

    // Attack:
    // Trigger BIST during runtime to corrupt critical data
    // Causes denial of service or exploitable state

endmodule

Fixed Code

// Fixed: Test mode with lifecycle restrictions

module secure_test_controller (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        test_mode_request,
    input  wire [31:0] test_addr,
    input  wire [31:0] test_wdata,
    input  wire        test_write,
    input  wire        test_read,

    // Lifecycle signals
    input  wire        boot_complete,         // Set when boot finishes
    input  wire        production_mode_fuse,  // OTP fuse for production

    // System interfaces
    output reg  [31:0] sys_addr,
    output reg  [31:0] sys_wdata,
    output reg         sys_write,
    output reg         sys_read,
    output reg  [31:0] sys_rdata,

    // Debug access (restricted)
    output reg         debug_mem_access,
    output reg         error_inject_enable,
    output reg         test_mode_denied
);

    // FIXED: Test mode only allowed during specific lifecycle stages
    wire test_allowed = !boot_complete &&      // Only before boot completes
                        !production_mode_fuse; // Only in development mode

    // FIXED: Actual test mode state
    reg test_mode_active;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            test_mode_active <= 1'b0;
            debug_mem_access <= 1'b0;
            error_inject_enable <= 1'b0;
            test_mode_denied <= 1'b0;
        end else begin
            test_mode_denied <= 1'b0;

            // FIXED: Check if test mode activation is allowed
            if (test_mode_request) begin
                if (test_allowed) begin
                    test_mode_active <= 1'b1;
                end else begin
                    // FIXED: Deny test mode after boot or in production
                    test_mode_active <= 1'b0;
                    test_mode_denied <= 1'b1;
                end
            end

            // FIXED: Test features only active when allowed
            if (test_mode_active && test_allowed) begin
                sys_addr <= test_addr;
                sys_wdata <= test_wdata;
                sys_write <= test_write;
                sys_read <= test_read;
                debug_mem_access <= 1'b1;
                error_inject_enable <= 1'b1;
            end else begin
                // FIXED: Disable test features when not allowed
                debug_mem_access <= 1'b0;
                error_inject_enable <= 1'b0;
                test_mode_active <= 1'b0;
            end
        end
    end

    // FIXED: Force disable test mode once boot completes
    always @(posedge boot_complete) begin
        test_mode_active <= 1'b0;
    end

endmodule

// Fixed: Debug logic with authentication and lifecycle check
module secure_cpu_debug (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        debug_request,
    input  wire [31:0] debug_addr,
    input  wire [31:0] debug_data,
    input  wire        debug_write,

    // FIXED: Lifecycle and authentication
    input  wire        debug_authenticated,   // Challenge-response completed
    input  wire        development_mode,      // From fuse
    input  wire [1:0]  lifecycle_state,       // 00=dev, 01=prod, 10=rma, 11=scrap

    // CPU internals
    output reg  [31:0] pc_override,
    output reg         halt_cpu,
    output reg         single_step,
    output reg  [31:0] register_write_data,
    output reg  [4:0]  register_write_addr,
    output reg         register_write_en,
    output reg         debug_denied
);

    // FIXED: Debug allowed only with proper authentication and lifecycle
    localparam LC_DEV = 2'b00;
    localparam LC_PROD = 2'b01;
    localparam LC_RMA = 2'b10;

    wire debug_allowed;

    // FIXED: Different capabilities per lifecycle state
    assign debug_allowed = (lifecycle_state == LC_DEV) ||
                           (lifecycle_state == LC_RMA && debug_authenticated);
    // Production mode never allows debug

    // FIXED: Limit capabilities even when allowed
    wire full_debug = (lifecycle_state == LC_DEV);
    wire limited_debug = (lifecycle_state == LC_RMA && debug_authenticated);

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            halt_cpu <= 1'b0;
            single_step <= 1'b0;
            register_write_en <= 1'b0;
            debug_denied <= 1'b0;
        end else begin
            register_write_en <= 1'b0;
            debug_denied <= 1'b0;

            if (debug_request) begin
                if (!debug_allowed) begin
                    // FIXED: Deny debug in production
                    debug_denied <= 1'b1;
                end else begin
                    case (debug_addr[7:0])
                        8'h00: begin
                            // Halt CPU - allowed in debug modes
                            halt_cpu <= debug_data[0];
                        end

                        8'h04: begin
                            // PC override - only in full debug
                            if (full_debug) begin
                                pc_override <= debug_data;
                            end else begin
                                debug_denied <= 1'b1;
                            end
                        end

                        8'h08: begin
                            // Register write - only in full debug
                            if (full_debug) begin
                                register_write_addr <= debug_data[4:0];
                                register_write_data <= debug_data;
                                register_write_en <= 1'b1;
                            end else begin
                                debug_denied <= 1'b1;
                            end
                        end

                        8'h0C: begin
                            // Single-step - allowed in limited debug
                            single_step <= debug_data[0];
                        end

                        default: begin
                            debug_denied <= 1'b1;
                        end
                    endcase
                end
            end
        end
    end

endmodule

// Fixed: BIST with runtime lockout
module secure_bist_controller (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        bist_start,
    input  wire [7:0]  bist_pattern,
    input  wire        boot_complete,        // FIXED: Lifecycle signal
    input  wire        bist_unlock_fuse,     // FIXED: Special unlock fuse
    output reg         bist_running,
    output reg         bist_complete,
    output reg         bist_pass,
    output reg         bist_denied
);

    reg [31:0] memory [0:1023];
    reg [9:0] test_addr;

    // FIXED: BIST only allowed before boot completes
    wire bist_allowed = !boot_complete || bist_unlock_fuse;

    // FIXED: Lock BIST after it completes
    reg bist_locked;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            bist_running <= 1'b0;
            bist_complete <= 1'b0;
            bist_locked <= 1'b0;
            bist_denied <= 1'b0;
        end else begin
            bist_denied <= 1'b0;

            if (bist_start && !bist_running) begin
                // FIXED: Check if BIST is allowed
                if (bist_allowed && !bist_locked) begin
                    bist_running <= 1'b1;
                    test_addr <= 10'b0;
                end else begin
                    // FIXED: Deny BIST after boot or when locked
                    bist_denied <= 1'b1;
                end
            end else if (bist_running) begin
                // Run BIST (only reaches here if allowed)
                memory[test_addr] <= {4{bist_pattern}};
                test_addr <= test_addr + 1;

                if (test_addr == 10'h3FF) begin
                    bist_complete <= 1'b1;
                    bist_running <= 1'b0;
                    // FIXED: Lock BIST after completion
                    bist_locked <= 1'b1;
                end
            end
        end
    end

    // FIXED: Also lock when boot completes
    always @(posedge boot_complete) begin
        if (!bist_unlock_fuse) begin
            bist_locked <= 1'b1;
        end
    end

endmodule

CVE Examples

  • CVE-2021-33150: Processor allows test or debug logic activation at runtime.
  • CVE-2021-0146: Processor debug logic enables privilege escalation during operation.
  • CVE-2019-0090: Intel CSME test mode accessible after secure boot.

  • CWE-284: Improper Access Control (parent)
  • CWE-1207: Debug and Test Problems (category)
  • CWE-1244: Internal Asset Exposed to Unsafe Debug Access (related)
  • CWE-1243: Sensitive Non-Volatile Information Not Protected During Debug (related)

References

  1. MITRE Corporation. "CWE-1313: Hardware Allows Activation of Test or Debug Logic at Runtime." https://cwe.mitre.org/data/definitions/1313.html
  2. NIST. "Platform Firmware Resiliency Guidelines"
  3. ARM. "CoreSight Debug and Trace Architecture"