Skip to content

Commit fcc2468

Browse files
zkoopmansgvisor-bot
authored andcommitted
Add CPUSet for runsc mitigate.
PiperOrigin-RevId: 355242055
1 parent 49f783f commit fcc2468

File tree

2 files changed

+336
-58
lines changed

2 files changed

+336
-58
lines changed

runsc/mitigate/cpu.go

+167-25
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package mitigate
1616

1717
import (
1818
"fmt"
19+
"io/ioutil"
1920
"regexp"
2021
"strconv"
2122
"strings"
@@ -31,16 +32,104 @@ const (
3132
)
3233

3334
const (
34-
processorKey = "processor"
35-
vendorIDKey = "vendor_id"
36-
cpuFamilyKey = "cpu family"
37-
modelKey = "model"
38-
coreIDKey = "core id"
39-
bugsKey = "bugs"
35+
processorKey = "processor"
36+
vendorIDKey = "vendor_id"
37+
cpuFamilyKey = "cpu family"
38+
modelKey = "model"
39+
physicalIDKey = "physical id"
40+
coreIDKey = "core id"
41+
bugsKey = "bugs"
4042
)
4143

42-
// getCPUSet returns cpu structs from reading /proc/cpuinfo.
43-
func getCPUSet(data string) ([]*cpu, error) {
44+
const (
45+
cpuOnlineTemplate = "/sys/devices/system/cpu/cpu%d/online"
46+
)
47+
48+
// cpuSet contains a map of all CPUs on the system, mapped
49+
// by Physical ID and CoreIDs. threads with the same
50+
// Core and Physical ID are Hyperthread pairs.
51+
type cpuSet map[cpuID]*threadGroup
52+
53+
// newCPUSet creates a CPUSet from data read from /proc/cpuinfo.
54+
func newCPUSet(data []byte, vulnerable func(*thread) bool) (cpuSet, error) {
55+
processors, err := getThreads(string(data))
56+
if err != nil {
57+
return nil, err
58+
}
59+
60+
set := make(cpuSet)
61+
for _, p := range processors {
62+
// Each ID is of the form physicalID:coreID. Hyperthread pairs
63+
// have identical physical and core IDs. We need to match
64+
// Hyperthread pairs so that we can shutdown all but one per
65+
// pair.
66+
core, ok := set[p.id]
67+
if !ok {
68+
core = &threadGroup{}
69+
set[p.id] = core
70+
}
71+
core.isVulnerable = core.isVulnerable || vulnerable(p)
72+
core.threads = append(core.threads, p)
73+
}
74+
return set, nil
75+
}
76+
77+
// String implements the String method for CPUSet.
78+
func (c cpuSet) String() string {
79+
ret := ""
80+
for _, tg := range c {
81+
ret += fmt.Sprintf("%s\n", tg)
82+
}
83+
return ret
84+
}
85+
86+
// getRemainingList returns the list of threads that will remain active
87+
// after mitigation.
88+
func (c cpuSet) getRemainingList() []*thread {
89+
threads := make([]*thread, 0, len(c))
90+
for _, core := range c {
91+
// If we're vulnerable, take only one thread from the pair.
92+
if core.isVulnerable {
93+
threads = append(threads, core.threads[0])
94+
continue
95+
}
96+
// Otherwise don't shutdown anything.
97+
threads = append(threads, core.threads...)
98+
}
99+
return threads
100+
}
101+
102+
// getShutdownList returns the list of threads that will be shutdown on
103+
// mitigation.
104+
func (c cpuSet) getShutdownList() []*thread {
105+
threads := make([]*thread, 0)
106+
for _, core := range c {
107+
// Only if we're vulnerable do shutdown anything. In this case,
108+
// shutdown all but the first entry.
109+
if core.isVulnerable && len(core.threads) > 1 {
110+
threads = append(threads, core.threads[1:]...)
111+
}
112+
}
113+
return threads
114+
}
115+
116+
// threadGroup represents Hyperthread pairs on the same physical/core ID.
117+
type threadGroup struct {
118+
threads []*thread
119+
isVulnerable bool
120+
}
121+
122+
// String implements the String method for threadGroup.
123+
func (c *threadGroup) String() string {
124+
ret := fmt.Sprintf("ThreadGroup:\nIsVulnerable: %t\n", c.isVulnerable)
125+
for _, processor := range c.threads {
126+
ret += fmt.Sprintf("%s\n", processor)
127+
}
128+
return ret
129+
}
130+
131+
// getThreads returns threads structs from reading /proc/cpuinfo.
132+
func getThreads(data string) ([]*thread, error) {
44133
// Each processor entry should start with the
45134
// processor key. Find the beginings of each.
46135
r := buildRegex(processorKey, `\d+`)
@@ -56,13 +145,13 @@ func getCPUSet(data string) ([]*cpu, error) {
56145
// indexes (e.g. data[index[i], index[i+1]]).
57146
// There should be len(indicies) - 1 CPUs
58147
// since the last index is the end of the string.
59-
var cpus = make([]*cpu, 0, len(indices)-1)
148+
var cpus = make([]*thread, 0, len(indices)-1)
60149
// Find each string that represents a CPU. These begin "processor".
61150
for i := 1; i < len(indices); i++ {
62151
start := indices[i-1][0]
63152
end := indices[i][0]
64153
// Parse the CPU entry, which should be between start/end.
65-
c, err := getCPU(data[start:end])
154+
c, err := newThread(data[start:end])
66155
if err != nil {
67156
return nil, err
68157
}
@@ -71,18 +160,25 @@ func getCPUSet(data string) ([]*cpu, error) {
71160
return cpus, nil
72161
}
73162

163+
// cpuID for each thread is defined by the physical and
164+
// core IDs. If equal, two threads are Hyperthread pairs.
165+
type cpuID struct {
166+
physicalID int64
167+
coreID int64
168+
}
169+
74170
// type cpu represents pertinent info about a cpu.
75-
type cpu struct {
171+
type thread struct {
76172
processorNumber int64 // the processor number of this CPU.
77173
vendorID string // the vendorID of CPU (e.g. AuthenticAMD).
78174
cpuFamily int64 // CPU family number (e.g. 6 for CascadeLake/Skylake).
79175
model int64 // CPU model number (e.g. 85 for CascadeLake/Skylake).
80-
coreID int64 // This CPU's core id to match Hyperthread Pairs
176+
id cpuID // id for this thread
81177
bugs map[string]struct{} // map of vulnerabilities parsed from the 'bugs' field.
82178
}
83179

84-
// getCPU parses a CPU from a single cpu entry from /proc/cpuinfo.
85-
func getCPU(data string) (*cpu, error) {
180+
// newThread parses a CPU from a single cpu entry from /proc/cpuinfo.
181+
func newThread(data string) (*thread, error) {
86182
processor, err := parseProcessor(data)
87183
if err != nil {
88184
return nil, err
@@ -103,6 +199,11 @@ func getCPU(data string) (*cpu, error) {
103199
return nil, err
104200
}
105201

202+
physicalID, err := parsePhysicalID(data)
203+
if err != nil {
204+
return nil, err
205+
}
206+
106207
coreID, err := parseCoreID(data)
107208
if err != nil {
108209
return nil, err
@@ -113,16 +214,41 @@ func getCPU(data string) (*cpu, error) {
113214
return nil, err
114215
}
115216

116-
return &cpu{
217+
return &thread{
117218
processorNumber: processor,
118219
vendorID: vendorID,
119220
cpuFamily: cpuFamily,
120221
model: model,
121-
coreID: coreID,
122-
bugs: bugs,
222+
id: cpuID{
223+
physicalID: physicalID,
224+
coreID: coreID,
225+
},
226+
bugs: bugs,
123227
}, nil
124228
}
125229

230+
// String implements the String method for thread.
231+
func (t *thread) String() string {
232+
template := `CPU: %d
233+
CPU ID: %+v
234+
Vendor: %s
235+
Family/Model: %d/%d
236+
Bugs: %s
237+
`
238+
bugs := make([]string, 0)
239+
for bug := range t.bugs {
240+
bugs = append(bugs, bug)
241+
}
242+
243+
return fmt.Sprintf(template, t.processorNumber, t.id, t.vendorID, t.cpuFamily, t.model, strings.Join(bugs, ","))
244+
}
245+
246+
// shutdown turns off the CPU by writing 0 to /sys/devices/cpu/cpu{N}/online.
247+
func (t *thread) shutdown() error {
248+
cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber)
249+
return ioutil.WriteFile(cpuPath, []byte{'0'}, 0644)
250+
}
251+
126252
// List of pertinent side channel vulnerablilites.
127253
// For mds, see: https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/mds.html.
128254
var vulnerabilities = []string{
@@ -134,35 +260,46 @@ var vulnerabilities = []string{
134260
}
135261

136262
// isVulnerable checks if a CPU is vulnerable to pertinent bugs.
137-
func (c *cpu) isVulnerable() bool {
263+
func (t *thread) isVulnerable() bool {
138264
for _, bug := range vulnerabilities {
139-
if _, ok := c.bugs[bug]; ok {
265+
if _, ok := t.bugs[bug]; ok {
140266
return true
141267
}
142268
}
143269
return false
144270
}
145271

272+
// isActive checks if a CPU is active from /sys/devices/system/cpu/cpu{N}/online
273+
// If the file does not exist (ioutil returns in error), we assume the CPU is on.
274+
func (t *thread) isActive() bool {
275+
cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber)
276+
data, err := ioutil.ReadFile(cpuPath)
277+
if err != nil {
278+
return true
279+
}
280+
return len(data) > 0 && data[0] != '0'
281+
}
282+
146283
// similarTo checks family/model/bugs fields for equality of two
147284
// processors.
148-
func (c *cpu) similarTo(other *cpu) bool {
149-
if c.vendorID != other.vendorID {
285+
func (t *thread) similarTo(other *thread) bool {
286+
if t.vendorID != other.vendorID {
150287
return false
151288
}
152289

153-
if other.cpuFamily != c.cpuFamily {
290+
if other.cpuFamily != t.cpuFamily {
154291
return false
155292
}
156293

157-
if other.model != c.model {
294+
if other.model != t.model {
158295
return false
159296
}
160297

161-
if len(other.bugs) != len(c.bugs) {
298+
if len(other.bugs) != len(t.bugs) {
162299
return false
163300
}
164301

165-
for bug := range c.bugs {
302+
for bug := range t.bugs {
166303
if _, ok := other.bugs[bug]; !ok {
167304
return false
168305
}
@@ -190,6 +327,11 @@ func parseModel(data string) (int64, error) {
190327
return parseIntegerResult(data, modelKey)
191328
}
192329

330+
// parsePhysicalID parses the physical id field.
331+
func parsePhysicalID(data string) (int64, error) {
332+
return parseIntegerResult(data, physicalIDKey)
333+
}
334+
193335
// parseCoreID parses the core id field.
194336
func parseCoreID(data string) (int64, error) {
195337
return parseIntegerResult(data, coreIDKey)

0 commit comments

Comments
 (0)