154 lines
4.5 KiB
Zig
154 lines
4.5 KiB
Zig
// References:
|
|
// [1] Security Analysis of x86 Processor Microcode https://www.dcddcc.com/docs/2014_paper_microcode.pdf
|
|
// [2] NetBSD sys/arch/x86/x86/cpu_ucode_amd.c
|
|
|
|
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const eql = std.mem.eql;
|
|
|
|
const cpu = @import("../cpu.zig");
|
|
const log = @import("../logger.zig").log;
|
|
const msr = @import("../msr.zig");
|
|
|
|
const MicrocodeError = @import("../microcode.zig").MicrocodeError;
|
|
|
|
// TODO Test
|
|
pub fn apply(microcode: []const u8) MicrocodeError!bool {
|
|
var mc = try Microcode.init(microcode);
|
|
const sign = cpu.CpuId.signature();
|
|
|
|
var rev_id: u16 = 0;
|
|
|
|
// Determine rev_id of processor
|
|
for (mc.cpu_equivalence_table) |entry| {
|
|
if (entry.processor_signature == sign.value) {
|
|
rev_id = entry.processor_rev_id;
|
|
}
|
|
}
|
|
|
|
if (rev_id == 0)
|
|
return false; // No patch available
|
|
|
|
// Find applicable patch
|
|
while (try mc.nextPatch()) |patch| {
|
|
if (patch.header.processor_rev_id == rev_id) {
|
|
log(.Debug, "[microcode/amd] Compatible microcode update found: {x}", .{patch.header.patch_id});
|
|
cpu.wrmsr(msr.ucode_amd_patchloader, @ptrToInt(&patch) + @sizeOf(PatchHeader));
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
pub fn parse(microcode: []const u8) MicrocodeError!Microcode {
|
|
return Microcode.init(microcode);
|
|
}
|
|
|
|
inline fn toU32(bytes: []const u8) u32 {
|
|
return @bytesToSlice(u32, bytes)[0];
|
|
}
|
|
|
|
const Microcode = struct {
|
|
const Self = @This();
|
|
const magic_value = [4]u8{ 'D', 'M', 'A', 0 };
|
|
|
|
cpu_equivalence_table: []CpuEquivalenceTable,
|
|
remaining_patches: []const u8,
|
|
|
|
pub fn init(microcode: []const u8) MicrocodeError!Self {
|
|
if (microcode.len < 4)
|
|
return MicrocodeError.EndOfData;
|
|
if (!eql(u8, microcode[0..4], &magic_value))
|
|
return MicrocodeError.Invalid;
|
|
if (microcode.len < 8)
|
|
return MicrocodeError.EndOfData;
|
|
if (toU32(microcode[4..8]) != 0)
|
|
return MicrocodeError.Invalid;
|
|
|
|
if (microcode.len < 12)
|
|
return MicrocodeError.EndOfData;
|
|
const table_size = toU32(microcode[8..12]);
|
|
const table_end = 12 + table_size;
|
|
|
|
return Self{
|
|
.cpu_equivalence_table = @bytesToSlice(CpuEquivalenceTable, microcode[12..table_end]),
|
|
.remaining_patches = microcode[table_end..],
|
|
};
|
|
}
|
|
|
|
// NOTE Should return ?(MicrocodeError!Patch)
|
|
// but that crashes the compiler
|
|
pub fn nextPatch(self: *Self) MicrocodeError!?Patch {
|
|
if (self.remaining_patches.len == 0)
|
|
return null;
|
|
|
|
if (self.remaining_patches.len < 4)
|
|
return MicrocodeError.EndOfData;
|
|
if (toU32(self.remaining_patches[0..4]) != 1)
|
|
return MicrocodeError.Invalid;
|
|
|
|
if (self.remaining_patches.len < 8)
|
|
return MicrocodeError.EndOfData;
|
|
const patch_size = toU32(self.remaining_patches[4..8]);
|
|
const patch_end = 8 + patch_size;
|
|
|
|
if (self.remaining_patches.len < patch_end)
|
|
return MicrocodeError.EndOfData;
|
|
const header = @ptrCast(*const PatchHeader, &self.remaining_patches[8]);
|
|
const microcode = self.remaining_patches[8 + @sizeOf(PatchHeader) .. patch_end];
|
|
|
|
if (header.patch_data_checksum != 0) {
|
|
// Determined through experimentation that 0 checksums always
|
|
// fail, these patches are probably the encrypted ones as
|
|
// mentioned in [1] 4.1.1
|
|
var checksum: u32 = 0;
|
|
for (@bytesToSlice(u32, microcode)) |v|
|
|
checksum +%= v;
|
|
|
|
if (checksum != header.patch_data_checksum)
|
|
return MicrocodeError.ChecksumMismatch;
|
|
}
|
|
|
|
self.remaining_patches = self.remaining_patches[patch_end..];
|
|
|
|
return Patch{
|
|
.header = header,
|
|
.microcode = microcode,
|
|
};
|
|
}
|
|
};
|
|
|
|
const CpuEquivalenceTable = packed struct {
|
|
processor_signature: u32,
|
|
errata_mask: u32,
|
|
errata_compare: u32,
|
|
processor_rev_id: u16,
|
|
reserved: u16,
|
|
};
|
|
|
|
const Patch = struct {
|
|
header: *const PatchHeader,
|
|
microcode: []const u8,
|
|
};
|
|
|
|
const PatchHeader = packed struct {
|
|
date: u32,
|
|
patch_id: u32,
|
|
patch_data_id: u16,
|
|
patch_data_len: u8,
|
|
init_flag: u8,
|
|
patch_data_checksum: u32,
|
|
nb_dev_id: u32,
|
|
sb_dev_id: u32,
|
|
processor_rev_id: u16,
|
|
nb_rev_id: u8,
|
|
sb_rev_id: u8,
|
|
bios_api_rev: u8,
|
|
_reserved1: u8, // Not a u24 or [3]u8 due to compiler bug
|
|
_reserved2: u8,
|
|
_reserved3: u8,
|
|
match_reg: [8]u32,
|
|
};
|