Skip to content

Commit

Permalink
Fix CPUVulnerabilities() reporting from sysfs (#532)
Browse files Browse the repository at this point in the history
Fix CPUVulnerabilities() reporting from sysfs
---------

Signed-off-by: Michal Wasilewski <mwasilewski@gmx.com>
Signed-off-by: Michal <mwasilewski@gmx.com>
Co-authored-by: Ben Kochie <superq@gmail.com>
  • Loading branch information
mwasilew2 and SuperQ committed Jun 15, 2023
1 parent 6d9dbc0 commit a76f400
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 33 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,11 @@ ensure the `fixtures` directory is up to date by removing the existing directory
extracting the ttar file using `make fixtures/.unpacked` or just `make test`.

```bash
rm -rf fixtures
rm -rf testdata/fixtures
make test
```

Next, make the required changes to the extracted files in the `fixtures` directory. When
the changes are complete, run `make update_fixtures` to create a new `fixtures.ttar` file
based on the updated `fixtures` directory. And finally, verify the changes using
`git diff fixtures.ttar`.
`git diff testdata/fixtures.ttar`.
81 changes: 50 additions & 31 deletions sysfs/vulnerability.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,33 +24,49 @@ import (
)

const (
notAffected = "Not Affected"
vulnerable = "Vulnerable"
mitigation = "Mitigation"
notAffected = "not affected" // based on: https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-devices-system-cpu
vulnerable = "vulnerable"
mitigation = "mitigation"
)

const (
VulnerabilityStateNotAffected = iota
VulnerabilityStateVulnerable
VulnerabilityStateMitigation
)

var (
// VulnerabilityHumanEncoding allows mapping the vulnerability state (encoded as an int) onto a human friendly
// string. It can be used by consumers of this library to expose to the user the state of the vulnerability.
VulnerabilityHumanEncoding = map[int]string{
VulnerabilityStateNotAffected: notAffected,
VulnerabilityStateVulnerable: vulnerable,
VulnerabilityStateMitigation: mitigation,
}
)

// CPUVulnerabilities retrieves a map of vulnerability names to their mitigations.
func (fs FS) CPUVulnerabilities() ([]Vulnerability, error) {
matches, err := filepath.Glob(fs.sys.Path("devices/system/cpu/vulnerabilities/*"))
func (fs FS) CPUVulnerabilities() (map[string]*Vulnerability, error) {
matchingFilepaths, err := filepath.Glob(fs.sys.Path("devices/system/cpu/vulnerabilities/*"))
if err != nil {
return nil, err
}

vulnerabilities := make([]Vulnerability, 0, len(matches))
for _, match := range matches {
name := filepath.Base(match)
vulnerabilities := make(map[string]*Vulnerability, len(matchingFilepaths))
for _, path := range matchingFilepaths {
filename := filepath.Base(path)

value, err := os.ReadFile(match)
rawContent, err := os.ReadFile(path)
if err != nil {
return nil, err
}

v, err := parseVulnerability(name, string(value))
v, err := parseVulnerability(filename, string(rawContent))
if err != nil {
return nil, err
}

vulnerabilities = append(vulnerabilities, v)
vulnerabilities[filename] = v
}

return vulnerabilities, nil
Expand All @@ -59,29 +75,32 @@ func (fs FS) CPUVulnerabilities() ([]Vulnerability, error) {
// Vulnerability represents a single vulnerability extracted from /sys/devices/system/cpu/vulnerabilities/.
type Vulnerability struct {
CodeName string
State string
State int
Mitigation string
}

func parseVulnerability(name, value string) (Vulnerability, error) {
v := Vulnerability{CodeName: name}
value = strings.TrimSpace(value)
if value == notAffected {
v.State = notAffected
return v, nil
}

if strings.HasPrefix(value, vulnerable) {
v.State = vulnerable
v.Mitigation = strings.TrimPrefix(strings.TrimPrefix(value, vulnerable), ": ")
return v, nil
}
func parseVulnerability(name, rawContent string) (*Vulnerability, error) {
v := &Vulnerability{CodeName: name}
rawContent = strings.TrimSpace(rawContent)
rawContentLower := strings.ToLower(rawContent)
switch {
case strings.HasPrefix(rawContentLower, notAffected):
v.State = VulnerabilityStateNotAffected
case strings.HasPrefix(rawContentLower, vulnerable):
v.State = VulnerabilityStateVulnerable
m := strings.Fields(rawContent)
if len(m) > 1 {
v.Mitigation = strings.Join(m[1:], " ")
}
case strings.HasPrefix(rawContentLower, mitigation):
v.State = VulnerabilityStateMitigation
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)

if strings.HasPrefix(value, mitigation) {
v.State = mitigation
v.Mitigation = strings.TrimPrefix(strings.TrimPrefix(value, mitigation), ": ")
return v, nil
}

return v, fmt.Errorf("unknown vulnerability state for %s: %s", name, value)
return v, nil
}
61 changes: 61 additions & 0 deletions sysfs/vulnerability_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright 2023 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build linux
// +build linux

package sysfs

import (
"fmt"
"reflect"
"testing"
)

func TestFS_CPUVulnerabilities(t *testing.T) {
sysFs, err := NewFS(sysTestFixtures)
if err != nil {
t.Fatal(fmt.Errorf("failed to get sysfs FS: %w", err))
}
got, err := sysFs.CPUVulnerabilities()
if err != nil {
t.Fatal(fmt.Errorf("failed to parse sysfs vulnerabilities files: %w", err))
}

tests := []struct {
name string
vulnerabilityName string
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},
{"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},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotVulnerability, ok := got[tt.vulnerabilityName]
if !ok && !tt.wantErr {
t.Errorf("CPUVulnerabilities() vulnerability %s not found", tt.vulnerabilityName)
}
if !reflect.DeepEqual(gotVulnerability, tt.want) {
t.Errorf("CPUVulnerabilities() gotVulnerability = %v, want %v", gotVulnerability, tt.want)
}
})
}
}
43 changes: 43 additions & 0 deletions testdata/fixtures.ttar
Original file line number Diff line number Diff line change
Expand Up @@ -13234,6 +13234,49 @@ Lines: 1
2
Mode: 664
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/sys/devices/system/cpu/vulnerabilities
Mode: 755
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/sys/devices/system/cpu/vulnerabilities/itlb_multihit
Lines: 1
Not affected
Mode: 444
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/sys/devices/system/cpu/vulnerabilities/mds
Lines: 1
Vulnerable
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/sys/devices/system/cpu/vulnerabilities/mmio_stale_data
Lines: 1
Vulnerable: Clear CPU buffers attempted, no microcode
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/sys/devices/system/cpu/vulnerabilities/retbleed
Lines: 1
Mitigation: untrained return thunk; SMT enabled with STIBP protection
Mode: 444
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/sys/devices/system/cpu/vulnerabilities/spec_store_bypass
Lines: 1
Mitigation: Speculative Store Bypass disabled via prctl
Mode: 444
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/sys/devices/system/cpu/vulnerabilities/spectre_v1
Lines: 1
Mitigation: usercopy/swapgs barriers and __user pointer sanitization
Mode: 444
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/sys/devices/system/cpu/vulnerabilities/spectre_v2
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/tsx_async_abort
Lines: 1
Not affected
Mode: 444
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Directory: fixtures/sys/devices/system/node
Mode: 775
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Expand Down

0 comments on commit a76f400

Please sign in to comment.