package collector import ( "bee/audit/internal/schema" "log/slog" "os" "os/exec" "path/filepath" "strings" ) var ( queryPCILSPCIDetail = func(bdf string) (string, error) { out, err := exec.Command("lspci", "-vv", "-s", bdf).Output() if err != nil { return "", err } return string(out), nil } readPCIVPDFile = func(bdf string) ([]byte, error) { return os.ReadFile(filepath.Join("/sys/bus/pci/devices", bdf, "vpd")) } ) func enrichPCIeWithPCISerials(devs []schema.HardwarePCIeDevice) []schema.HardwarePCIeDevice { enriched := 0 for i := range devs { if !shouldProbePCIeSerial(devs[i]) { continue } bdf := normalizePCIeBDF(*devs[i].BDF) if bdf == "" { continue } if serial := queryPCIDeviceSerial(bdf); serial != "" { devs[i].SerialNumber = &serial enriched++ } } if enriched > 0 { slog.Info("pcie: serials enriched", "count", enriched) } return devs } func shouldProbePCIeSerial(dev schema.HardwarePCIeDevice) bool { if dev.BDF == nil || dev.SerialNumber != nil { return false } if dev.DeviceClass == nil { return false } class := strings.TrimSpace(*dev.DeviceClass) return isNICClass(class) || isGPUClass(class) } func queryPCIDeviceSerial(bdf string) string { if out, err := queryPCILSPCIDetail(bdf); err == nil { if serial := parseLSPCIDetailSerial(out); serial != "" { return serial } } if raw, err := readPCIVPDFile(bdf); err == nil { return parsePCIVPDSerial(raw) } return "" } func parseLSPCIDetailSerial(raw string) string { for _, line := range strings.Split(raw, "\n") { line = strings.TrimSpace(line) if line == "" { continue } lower := strings.ToLower(line) if !strings.Contains(lower, "serial number:") { continue } idx := strings.Index(line, ":") if idx < 0 { continue } if serial := strings.TrimSpace(line[idx+1:]); serial != "" { return serial } } return "" } func parsePCIVPDSerial(raw []byte) string { for i := 0; i+3 < len(raw); i++ { if raw[i] != 'S' || raw[i+1] != 'N' { continue } length := int(raw[i+2]) if length <= 0 || length > 64 || i+3+length > len(raw) { continue } value := strings.TrimSpace(strings.Trim(string(raw[i+3:i+3+length]), "\x00")) if !looksLikeSerial(value) { continue } return value } return "" } func looksLikeSerial(value string) bool { if len(value) < 4 { return false } hasAlphaNum := false for _, r := range value { switch { case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z', r >= '0' && r <= '9': hasAlphaNum = true case strings.ContainsRune(" -_./:", r): default: return false } } return hasAlphaNum }