Repository: prometheus/procfs
Pull request #568 (2023-09-15 13:16:30)

Improve sysfs vulnerability parsing
The existing sysfs vulnerability parsing routines expected the data provided by the kernel to start with either "Not Affected"/"Vulnerable"/"Mitigation"; however, there are a handful of vulnerabilities that can provide data not matching this expectation:

- https://elixir.bootlin.com/linux/v6.1.53/source/arch/x86/kernel/cpu/bugs.c#L2519
- https://elixir.bootlin.com/linux/v6.1.53/source/arch/x86/kernel/cpu/bugs.c#L546
- https://elixir.bootlin.com/linux/v6.1.53/source/arch/x86/kernel/cpu/bugs.c#L2578

Testing the existing implementation of a VM results in a error being generated:

````
root@debian:~/node_exporter# ./node_exporter --collector.cpu_vulnerabilities &
root@debian:~/node_exporter# curl localhost:9100/metrics | grep cpu
2023-09-14T09:12:18.615Z caller=collector.go:169 level=error msg="collector failed" name=cpu_vulnerabilities duration_seconds=0.000116497 err="failed to get vulnerabilities: unknown vulnerability state for itlb_multihit: KVM: Mitigation: VMX unsupported"
````

This PR modifies the vulnerability parsing to make use of a 4th state ("Unknown"), which is used when the vulnerability information can't be parsed to any of the other vulnerability states, and output the information provided by the kernel, rather than erroring out.

Vulnerability parsing tests have been updated to include the aforementioned vulnerability data.

SuperQ: CI fix is here: https://github.com/prometheus/procfs/pull/573

Patch:
diff --git a/sysfs/vulnerability.go b/sysfs/vulnerability.go
index 08927a2..5a14a3e 100644
--- a/sysfs/vulnerability.go
+++ b/sysfs/vulnerability.go
@@ -17,7 +17,6 @@
 package sysfs
 
 import (
-	"fmt"
 	"os"
 	"path/filepath"
 	"strings"
@@ -27,12 +26,14 @@ const (
 	notAffected = "not affected" // based on: https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu
 	vulnerable  = "vulnerable"
 	mitigation  = "mitigation"
+	unknown     = "unknown"
 )
 
 const (
 	VulnerabilityStateNotAffected = iota
 	VulnerabilityStateVulnerable
 	VulnerabilityStateMitigation
+	VulnerabilityStateUnknown
 )
 
 var (
@@ -42,6 +43,7 @@ var (
 		VulnerabilityStateNotAffected: notAffected,
 		VulnerabilityStateVulnerable:  vulnerable,
 		VulnerabilityStateMitigation:  mitigation,
+		VulnerabilityStateUnknown:     unknown,
 	}
 )
 
@@ -98,9 +100,17 @@ func parseVulnerability(name, rawContent string) (*Vulnerability, error) {
 		if len(m) > 1 {
 			v.Mitigation = strings.Join(m[1:], " ")
 		}
+	case strings.HasPrefix(rawContentLower, unknown):
+		v.State = VulnerabilityStateUnknown
+		m := strings.Fields(rawContent)
+		if len(m) > 1 {
+			v.Mitigation = strings.Join(m[1:], " ")
+		}
 	default:
-		return nil, fmt.Errorf("unknown vulnerability state for %s: %s", name, rawContent)
-
+		// Output the raw data obtained from the vulnerability, with state
+		// unknown, rather than erroring out
+		v.State = VulnerabilityStateUnknown
+		v.Mitigation = rawContent
 	}
 	return v, nil
 }


Test patch:
diff --git a/sysfs/vulnerability_test.go b/sysfs/vulnerability_test.go
index 255586d..ec9cafc 100644
--- a/sysfs/vulnerability_test.go
+++ b/sysfs/vulnerability_test.go
@@ -38,14 +38,15 @@ func TestFS_CPUVulnerabilities(t *testing.T) {
 		want              *Vulnerability
 		wantErr           bool
 	}{
-		{"Not affected", "itlb_multihit", &Vulnerability{CodeName: "itlb_multihit", State: VulnerabilityStateNotAffected, Mitigation: ""}, false},
-		{"Not affected with underscores", "tsx_async_abort", &Vulnerability{CodeName: "tsx_async_abort", State: VulnerabilityStateNotAffected, Mitigation: ""}, false},
+		{"Not affected", "tsx_async_abort", &Vulnerability{CodeName: "tsx_async_abort", State: VulnerabilityStateNotAffected, Mitigation: ""}, false},
 		{"Mitigation simple string", "spec_store_bypass", &Vulnerability{CodeName: "spec_store_bypass", State: VulnerabilityStateMitigation, Mitigation: "Speculative Store Bypass disabled via prctl"}, false},
 		{"Mitigation special chars", "retbleed", &Vulnerability{CodeName: "retbleed", State: VulnerabilityStateMitigation, Mitigation: "untrained return thunk; SMT enabled with STIBP protection"}, false},
 		{"Mitigation more special chars", "spectre_v1", &Vulnerability{CodeName: "spectre_v1", State: VulnerabilityStateMitigation, Mitigation: "usercopy/swapgs barriers and __user pointer sanitization"}, false},
 		{"Mitigation with multiple subsections", "spectre_v2", &Vulnerability{CodeName: "spectre_v2", State: VulnerabilityStateMitigation, Mitigation: "Retpolines, IBPB: conditional, STIBP: always-on, RSB filling, PBRSB-eIBRS: Not affected"}, false},
 		{"Vulnerable", "mds", &Vulnerability{CodeName: "mds", State: VulnerabilityStateVulnerable, Mitigation: ""}, false},
 		{"Vulnerable with mitigation available", "mmio_stale_data", &Vulnerability{CodeName: "mmio_stale_data", State: VulnerabilityStateVulnerable, Mitigation: "Clear CPU buffers attempted, no microcode"}, false},
+		{"Unknown", "srbds", &Vulnerability{CodeName: "srbds", State: VulnerabilityStateUnknown, Mitigation: "Dependent on hypervisor status"}, false},
+		{"Unknown with unparseable mitigation", "itlb_multihit", &Vulnerability{CodeName: "itlb_multihit", State: VulnerabilityStateUnknown, Mitigation: "KVM: Mitigation: VMX unsupported"}, false},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar
index ae2e0f0..39ec946 100644
--- a/testdata/fixtures.ttar
+++ b/testdata/fixtures.ttar
@@ -13239,7 +13239,7 @@ Mode: 755
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/sys/devices/system/cpu/vulnerabilities/itlb_multihit
 Lines: 1
-Not affected
+KVM: Mitigation: VMX unsupported
 Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/sys/devices/system/cpu/vulnerabilities/mds
@@ -13272,6 +13272,11 @@ Lines: 1
 Mitigation: Retpolines, IBPB: conditional, STIBP: always-on, RSB filling, PBRSB-eIBRS: Not affected
 Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/devices/system/cpu/vulnerabilities/srbds
+Lines: 1
+Unknown: Dependent on hypervisor status
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/sys/devices/system/cpu/vulnerabilities/tsx_async_abort
 Lines: 1
 Not affected