Files
bee/audit/internal/collector/dmesg_events.go
T

155 lines
4.4 KiB
Go

package collector
import (
"bee/audit/internal/schema"
"log/slog"
"os/exec"
"regexp"
"strings"
"time"
)
// dmesg -T output: [Thu Jun 18 14:23:45 2026] message
// dmesg without -T: [ 123.456789] message
var dmesgTimestampRE = regexp.MustCompile(`^\[([^\]]+)\]\s*(.*)$`)
// Keywords that indicate an error or hardware problem worth capturing.
var dmesgErrorPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)\berr(or)?\b`),
regexp.MustCompile(`(?i)\bfail(ed|ure)?\b`),
regexp.MustCompile(`(?i)\bfault\b`),
regexp.MustCompile(`(?i)\bwarn(ing)?\b`),
regexp.MustCompile(`(?i)\bAER\b`),
regexp.MustCompile(`(?i)\bXid\b`),
regexp.MustCompile(`(?i)\bNVRM\b`),
regexp.MustCompile(`(?i)\bpanic\b`),
regexp.MustCompile(`(?i)\bcorrected\b`),
regexp.MustCompile(`(?i)\buncorrect`),
regexp.MustCompile(`(?i)\bECC\b`),
regexp.MustCompile(`(?i)\btimeout\b`),
regexp.MustCompile(`(?i)\breset\b`),
regexp.MustCompile(`(?i)\bdead\b`),
regexp.MustCompile(`(?i)\bhang\b`),
regexp.MustCompile(`(?i)\bstall\b`),
regexp.MustCompile(`(?i)\bdisabled\b`),
}
// Boot-time messages below are noisy configuration/driver diagnostics, not
// hardware incidents. Raw dmesg remains in the bundle, but presenting each
// of them as Critical makes the event log unusable.
var dmesgIgnorePatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)bridge window .* failed to assign`),
regexp.MustCompile(`(?i)^NVRM: loading NVIDIA .* Kernel Module`),
regexp.MustCompile(`(?i)^NVRM: Persistence mode is deprecated`),
regexp.MustCompile(`(?i)^nvidia: module verification failed:.*tainting kernel`),
regexp.MustCompile(`(?i)^Yama: disabled by default`),
regexp.MustCompile(`(?i)^ERST: .*initialized`),
regexp.MustCompile(`(?i)iommu sva bind failed: -95`),
regexp.MustCompile(`(?i)gpuClearFbhubPoisonIntrForBug`),
}
// collectDmesgErrors runs `dmesg -T` (or `dmesg` without -T on failure) and
// returns only lines that match known error/warning patterns.
func collectDmesgErrors() []schema.HardwareEventLog {
out, err := exec.Command("dmesg", "-T").Output()
if err != nil || len(out) == 0 {
// Fallback: dmesg without human-readable timestamps
out, err = exec.Command("dmesg").Output()
if err != nil || len(out) == 0 {
return nil
}
}
entries := parseDmesgErrors(string(out))
if len(entries) == 0 {
return nil
}
slog.Info("dmesg: collected error entries", "count", len(entries))
return entries
}
func parseDmesgErrors(output string) []schema.HardwareEventLog {
var entries []schema.HardwareEventLog
collectedAt := time.Now().UTC().Format(time.RFC3339)
for _, line := range strings.Split(output, "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
var timestamp, message string
if m := dmesgTimestampRE.FindStringSubmatch(line); m != nil {
timestamp = strings.TrimSpace(m[1])
message = strings.TrimSpace(m[2])
} else {
message = line
}
if message == "" {
continue
}
if !matchesAny(message, dmesgErrorPatterns) {
continue
}
if matchesAny(message, dmesgIgnorePatterns) {
continue
}
severity := dmesgSeverity(message)
source := "dmesg"
var eventTime *string
if timestamp != "" {
t := timestamp
eventTime = &t
} else {
eventTime = &collectedAt
}
entries = append(entries, schema.HardwareEventLog{
Source: source,
EventTime: eventTime,
Severity: &severity,
Message: message,
})
}
return entries
}
func matchesAny(s string, patterns []*regexp.Regexp) bool {
for _, p := range patterns {
if p.MatchString(s) {
return true
}
}
return false
}
// dmesgSeverityCriticalPatterns use \b word boundaries, unlike a plain
// strings.Contains check, so common words that merely contain a keyword as a
// substring don't false-positive — e.g. "disabled by default" contains
// "fault" (de-fault), and "undead" would contain "dead".
var dmesgSeverityCriticalPatterns = []*regexp.Regexp{
regexp.MustCompile(`(?i)\bpanic\b`),
regexp.MustCompile(`(?i)\buncorrect`),
regexp.MustCompile(`(?i)\bxid\b`),
regexp.MustCompile(`(?i)\berror\b`),
regexp.MustCompile(`(?i)\bfault\b`),
regexp.MustCompile(`(?i)\bfail(ed|ure)?\b`),
regexp.MustCompile(`(?i)\bdead\b`),
regexp.MustCompile(`(?i)\bhang\b`),
}
func dmesgSeverity(msg string) string {
if sev, ok := XidSeverity(msg); ok {
if sev == "critical" {
return statusCritical
}
return statusWarning
}
if matchesAny(msg, dmesgSeverityCriticalPatterns) {
return statusCritical
}
return statusWarning
}