Security Version Number Mutable to Older Versions

Description

Security Version Number Mutable to Older Versions occurs when the security-version number in hardware is mutable, resulting in the ability to downgrade (roll-back) the boot firmware to vulnerable code versions. Systems-on-Chip (SoC) implementing secure or verified boot may support security version numbers to prevent firmware downgrades. When these version numbers are modifiable, attackers can execute roll-back attacks, reverting to exploitable firmware versions or blocking legitimate upgrades via denial-of-service.

Risk

Mutable security version numbers have severe implications. Roll-back to vulnerable firmware. Known exploits become usable again. Downgrade attacks enabled. Security patches nullified. Upgrade denial-of-service. Authentication bypass through older firmware. Integrity compromise. Confidentiality breach through known vulnerabilities. High likelihood when version storage is not properly protected.

Solution

Store security version data in read-only registers or implement strict access controls preventing untrusted modification during architecture and design phase. Validate that version data remains read-only and access controls function correctly during testing implementation phase. Use OTP fuses for version counters. Implement monotonic counters that can only increment.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Roll-back to vulnerable firmware compromises system integrity.
ConfidentialityScope: Confidentiality

Older firmware may have known vulnerabilities exposing data.
AuthenticationScope: Authentication

Downgrade attacks can bypass security improvements in newer versions.

Example Code

Vulnerable Code

// Vulnerable: Mutable security version counter

module vulnerable_version_counter (
    input  wire        clk,
    input  wire        rst_n,

    // Version write interface
    input  wire [15:0] version_write_data,
    input  wire        version_write_en,

    // Current version
    output reg  [15:0] current_version,

    // Firmware verification
    input  wire [15:0] firmware_version,
    input  wire        verify_firmware,
    output reg         firmware_allowed
);

    // VULNERABLE: Version stored in regular register
    // Can be modified to any value

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            current_version <= 16'h0001;
        end else if (version_write_en) begin
            // VULNERABLE: Any write allowed, including downgrades
            current_version <= version_write_data;
        end
    end

    // Firmware version check
    always @(posedge clk) begin
        if (verify_firmware) begin
            // VULNERABLE: Compares against modifiable version
            firmware_allowed <= (firmware_version >= current_version);
        end
    end

    // Attack:
    // 1. current_version = 10 (requires firmware v10+)
    // 2. Attacker sets current_version = 1
    // 3. Old vulnerable firmware v5 now passes check
    // 4. System boots with known vulnerabilities

endmodule

// Vulnerable: Software-managed version counter
module vulnerable_software_version (
    input  wire        clk,
    input  wire        rst_n,

    // Memory-mapped register interface
    input  wire [31:0] reg_addr,
    input  wire [31:0] reg_wdata,
    input  wire        reg_write,
    input  wire        reg_read,
    output reg  [31:0] reg_rdata
);

    // VULNERABLE: Version in regular memory-mapped register
    reg [15:0] security_version;

    localparam VERSION_REG_ADDR = 32'h0000_0100;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            security_version <= 16'h0001;
        end else if (reg_write && reg_addr == VERSION_REG_ADDR) begin
            // VULNERABLE: Software can write any value
            security_version <= reg_wdata[15:0];
        end
    end

    always @(*) begin
        if (reg_read && reg_addr == VERSION_REG_ADDR) begin
            reg_rdata = {16'b0, security_version};
        end else begin
            reg_rdata = 32'b0;
        end
    end

    // Attack: Malicious software writes 0 to version register
    // All firmware versions now accepted

endmodule
// Vulnerable: Software version management without protection

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

// VULNERABLE: Version stored in regular memory
static uint16_t current_security_version = 1;

// VULNERABLE: Can set version to any value
void vulnerable_set_security_version(uint16_t version) {
    current_security_version = version;
}

bool vulnerable_check_firmware_version(uint16_t firmware_version) {
    // VULNERABLE: Compares against modifiable version
    return firmware_version >= current_security_version;
}

// Attack:
// vulnerable_set_security_version(0);
// Now any firmware version passes the check

Fixed Code

// Fixed: Immutable monotonic security version counter

module secure_version_counter (
    input  wire        clk,
    input  wire        rst_n,

    // Version increment interface (increment only)
    input  wire        version_increment,
    input  wire        privileged_access,
    input  wire        provisioning_mode,

    // Current version (read-only)
    output wire [15:0] current_version,

    // Firmware verification
    input  wire [15:0] firmware_version,
    input  wire        verify_firmware,
    output reg         firmware_allowed,
    output reg         version_update_denied
);

    // FIXED: OTP fuse-based counter (simulated)
    // Each fuse represents one version increment
    reg [15:0] version_fuses;
    reg        version_locked;

    // FIXED: Count set bits to get version number
    function [15:0] count_bits;
        input [15:0] fuses;
        integer i;
        begin
            count_bits = 0;
            for (i = 0; i < 16; i = i + 1) begin
                count_bits = count_bits + fuses[i];
            end
        end
    endfunction

    assign current_version = count_bits(version_fuses);

    // FIXED: Version can only increment (monotonic)
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            version_fuses <= 16'h0001;  // Start at version 1
            version_update_denied <= 1'b0;
            version_locked <= 1'b0;
        end else if (version_increment) begin
            version_update_denied <= 1'b0;

            // FIXED: Require privilege
            if (!privileged_access) begin
                version_update_denied <= 1'b1;
            end
            // FIXED: Check if locked
            else if (version_locked) begin
                version_update_denied <= 1'b1;
            end
            else begin
                // FIXED: Find next unset bit and set it (OTP behavior)
                // Version can only increase
                if (version_fuses[0] == 0)
                    version_fuses[0] <= 1'b1;
                else if (version_fuses[1] == 0)
                    version_fuses[1] <= 1'b1;
                // ... continue for all bits
                else if (version_fuses[15] == 0)
                    version_fuses[15] <= 1'b1;
                else
                    version_update_denied <= 1'b1;  // Max version reached
            end
        end
    end

    // FIXED: Lock version counter
    always @(posedge clk) begin
        if (provisioning_mode && privileged_access && version_increment) begin
            version_locked <= 1'b1;
        end
    end

    // FIXED: Firmware version check against immutable counter
    always @(posedge clk) begin
        if (verify_firmware) begin
            firmware_allowed <= (firmware_version >= current_version);
        end
    end

endmodule

// Fixed: Anti-rollback with hardware protection
module secure_antirollback_counter (
    input  wire        clk,
    input  wire        rst_n,

    // OTP interface
    output reg  [7:0]  otp_addr,
    output reg  [31:0] otp_wdata,
    output reg         otp_program,
    input  wire        otp_program_done,
    input  wire [31:0] otp_rdata,

    // Version interface
    input  wire        read_version,
    input  wire        increment_version,
    input  wire        trusted_source,
    output reg  [31:0] current_version,
    output reg         operation_complete,
    output reg         operation_denied
);

    // FIXED: State machine for OTP operations
    localparam IDLE = 0, READ_VERSION = 1, INCREMENT = 2, DONE = 3;
    reg [1:0] state;

    localparam VERSION_OTP_ADDR = 8'h10;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= IDLE;
            current_version <= 32'b0;
            operation_complete <= 1'b0;
            operation_denied <= 1'b0;
            otp_program <= 1'b0;
        end else begin
            case (state)
                IDLE: begin
                    operation_complete <= 1'b0;
                    operation_denied <= 1'b0;

                    if (read_version) begin
                        otp_addr <= VERSION_OTP_ADDR;
                        state <= READ_VERSION;
                    end
                    else if (increment_version) begin
                        // FIXED: Only trusted source can increment
                        if (trusted_source) begin
                            otp_addr <= VERSION_OTP_ADDR;
                            state <= INCREMENT;
                        end else begin
                            operation_denied <= 1'b1;
                        end
                    end
                end

                READ_VERSION: begin
                    // Read current version from OTP
                    current_version <= otp_rdata;
                    state <= DONE;
                end

                INCREMENT: begin
                    // FIXED: Increment using OTP (one-way)
                    // Write incremented value
                    otp_wdata <= otp_rdata + 1;
                    otp_program <= 1'b1;

                    if (otp_program_done) begin
                        otp_program <= 1'b0;
                        current_version <= otp_rdata + 1;
                        state <= DONE;
                    end
                end

                DONE: begin
                    operation_complete <= 1'b1;
                    state <= IDLE;
                end
            endcase
        end
    end

endmodule
// Fixed: Secure version management with hardware backing

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

// FIXED: Hardware OTP addresses
#define OTP_VERSION_ADDR    0x10000100
#define OTP_VERSION_LOCK    0x10000104

// FIXED: Read version from hardware OTP (immutable)
static uint16_t read_hardware_version(void) {
    volatile uint16_t* otp = (volatile uint16_t*)OTP_VERSION_ADDR;
    return *otp;
}

// FIXED: Check if version is locked
static bool is_version_locked(void) {
    volatile uint32_t* lock = (volatile uint32_t*)OTP_VERSION_LOCK;
    return (*lock & 1) != 0;
}

// FIXED: Increment version (can only increase)
bool secure_increment_version(void) {
    if (is_version_locked()) {
        return false;  // Cannot modify locked version
    }

    uint16_t current = read_hardware_version();

    // FIXED: OTP can only set bits, ensuring monotonic increase
    // This is handled by hardware, but we enforce it in software too
    uint16_t new_version = current + 1;

    // FIXED: Call secure hardware API to program OTP
    // This requires secure world privilege
    if (!program_otp_version(new_version)) {
        return false;
    }

    return true;
}

// FIXED: Check firmware version against hardware counter
bool secure_check_firmware_version(uint16_t firmware_version) {
    uint16_t required_version = read_hardware_version();

    // FIXED: Compares against hardware-backed immutable version
    return firmware_version >= required_version;
}

// FIXED: Secure boot integration
bool secure_boot_check(const uint8_t* firmware_header) {
    // Extract version from signed firmware header
    uint16_t firmware_version = extract_version(firmware_header);

    // FIXED: Anti-rollback check
    if (!secure_check_firmware_version(firmware_version)) {
        // Firmware is older than minimum allowed version
        return false;
    }

    // Proceed with signature verification...
    return verify_signature(firmware_header);
}

// FIXED: After successful boot, update version
void secure_update_version_after_boot(uint16_t booted_version) {
    uint16_t current = read_hardware_version();

    // FIXED: Only update if booted version is newer
    if (booted_version > current) {
        // This prevents rollback to this version
        while (read_hardware_version() < booted_version) {
            secure_increment_version();
        }
    }
}

CVE Examples

  • CVE-2020-0069: MediaTek chipsets allowed security version rollback enabling known exploits.
  • CVE-2019-11478: Linux kernel TCP vulnerability exploitable through firmware rollback.

  • CWE-285: Improper Authorization (parent)
  • CWE-757: Selection of Less-Secure Algorithm During Negotiation (peer)
  • CWE-1196: Security Flow Issues (category)
  • CAPEC-176: Configuration/Environment Manipulation

References

  1. MITRE Corporation. "CWE-1328: Security Version Number Mutable to Older Versions." https://cwe.mitre.org/data/definitions/1328.html
  2. ARM. "Trusted Firmware Anti-Rollback Protection"
  3. NIST. "Platform Firmware Resiliency Guidelines"