diff --git a/audit/internal/collector/board.go b/audit/internal/collector/board.go index f5e870b..bac4421 100644 --- a/audit/internal/collector/board.go +++ b/audit/internal/collector/board.go @@ -8,6 +8,14 @@ import ( "strings" ) +var execDmidecode = func(typeNum string) (string, error) { + out, err := exec.Command("dmidecode", "-t", typeNum).Output() + if err != nil { + return "", err + } + return string(out), nil +} + // collectBoard runs dmidecode for types 0, 1, 2 and returns the board record // plus the BIOS firmware entry. Any failure is logged and returns zero values. func collectBoard() (schema.HardwareBoard, []schema.HardwareFirmwareRecord) { @@ -141,9 +149,5 @@ func cleanDMIValue(v string) string { // runDmidecode executes dmidecode -t and returns its stdout. func runDmidecode(typeNum string) (string, error) { - out, err := exec.Command("dmidecode", "-t", typeNum).Output() - if err != nil { - return "", err - } - return string(out), nil + return execDmidecode(typeNum) } diff --git a/audit/internal/collector/collector.go b/audit/internal/collector/collector.go index 8089899..c04ef8a 100644 --- a/audit/internal/collector/collector.go +++ b/audit/internal/collector/collector.go @@ -24,9 +24,7 @@ func Run(_ runtimeenv.Mode) schema.HardwareIngestRequest { snap.Board = board snap.Firmware = append(snap.Firmware, biosFW...) - cpus, cpuFW := collectCPUs(snap.Board.SerialNumber) - snap.CPUs = cpus - snap.Firmware = append(snap.Firmware, cpuFW...) + snap.CPUs = collectCPUs(snap.Board.SerialNumber) snap.Memory = collectMemory() sensorDoc, err := readSensorsJSONDoc() diff --git a/audit/internal/collector/cpu.go b/audit/internal/collector/cpu.go index 8fbd2d2..3d71f18 100644 --- a/audit/internal/collector/cpu.go +++ b/audit/internal/collector/cpu.go @@ -6,30 +6,28 @@ import ( "fmt" "log/slog" "os" + "path/filepath" "strconv" "strings" ) -// collectCPUs runs dmidecode -t 4 and reads microcode version from sysfs. -func collectCPUs(boardSerial string) ([]schema.HardwareCPU, []schema.HardwareFirmwareRecord) { +// collectCPUs runs dmidecode -t 4 and enriches CPUs with microcode from sysfs. +func collectCPUs(boardSerial string) []schema.HardwareCPU { out, err := runDmidecode("4") if err != nil { slog.Warn("cpu: dmidecode type 4 failed", "err", err) - return nil, nil + return nil } cpus := parseCPUs(out, boardSerial) - - var firmware []schema.HardwareFirmwareRecord if mc := readMicrocode(); mc != "" { - firmware = append(firmware, schema.HardwareFirmwareRecord{ - DeviceName: "CPU Microcode", - Version: mc, - }) + for i := range cpus { + cpus[i].Firmware = &mc + } } slog.Info("cpu: collected", "count", len(cpus)) - return cpus, firmware + return cpus } // parseCPUs splits dmidecode output into per-processor sections and parses each. @@ -180,7 +178,7 @@ func parseInt(v string) int { // readMicrocode reads the CPU microcode revision from sysfs. // Returns empty string if unavailable. func readMicrocode() string { - data, err := os.ReadFile("/sys/devices/system/cpu/cpu0/microcode/version") + data, err := os.ReadFile(filepath.Join(cpuSysBaseDir, "cpu0", "microcode", "version")) if err != nil { return "" } diff --git a/audit/internal/collector/cpu_test.go b/audit/internal/collector/cpu_test.go index a93863d..8ff2250 100644 --- a/audit/internal/collector/cpu_test.go +++ b/audit/internal/collector/cpu_test.go @@ -1,6 +1,8 @@ package collector import ( + "os" + "path/filepath" "testing" ) @@ -63,6 +65,39 @@ func TestParseCPUs_unpopulated_skipped(t *testing.T) { } } +func TestCollectCPUsSetsFirmwareFromMicrocode(t *testing.T) { + tmp := t.TempDir() + origBase := cpuSysBaseDir + cpuSysBaseDir = tmp + t.Cleanup(func() { cpuSysBaseDir = origBase }) + + if err := os.MkdirAll(filepath.Join(tmp, "cpu0", "microcode"), 0755); err != nil { + t.Fatalf("mkdir microcode dir: %v", err) + } + if err := os.WriteFile(filepath.Join(tmp, "cpu0", "microcode", "version"), []byte("0x2b000643\n"), 0644); err != nil { + t.Fatalf("write microcode version: %v", err) + } + + origRun := execDmidecode + execDmidecode = func(typeNum string) (string, error) { + if typeNum != "4" { + t.Fatalf("unexpected dmidecode type: %s", typeNum) + } + return mustReadFile(t, "testdata/dmidecode_type4.txt"), nil + } + t.Cleanup(func() { execDmidecode = origRun }) + + cpus := collectCPUs("CAR315KA0803B90") + if len(cpus) != 2 { + t.Fatalf("expected 2 CPUs, got %d", len(cpus)) + } + for i, cpu := range cpus { + if cpu.Firmware == nil || *cpu.Firmware != "0x2b000643" { + t.Fatalf("cpu[%d] firmware=%v want microcode", i, cpu.Firmware) + } + } +} + func TestParseCPUStatus(t *testing.T) { tests := []struct { input string