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
| Impact | Details |
|---|---|
| Integrity | Scope: Integrity Roll-back to vulnerable firmware compromises system integrity. |
| Confidentiality | Scope: Confidentiality Older firmware may have known vulnerabilities exposing data. |
| Authentication | Scope: 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.
Related CWEs
- 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
- MITRE Corporation. "CWE-1328: Security Version Number Mutable to Older Versions." https://cwe.mitre.org/data/definitions/1328.html
- ARM. "Trusted Firmware Anti-Rollback Protection"
- NIST. "Platform Firmware Resiliency Guidelines"