@@ -16,6 +16,7 @@ package mitigate
16
16
17
17
import (
18
18
"fmt"
19
+ "io/ioutil"
19
20
"regexp"
20
21
"strconv"
21
22
"strings"
@@ -31,16 +32,104 @@ const (
31
32
)
32
33
33
34
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"
40
42
)
41
43
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:\n IsVulnerable: %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 ) {
44
133
// Each processor entry should start with the
45
134
// processor key. Find the beginings of each.
46
135
r := buildRegex (processorKey , `\d+` )
@@ -56,13 +145,13 @@ func getCPUSet(data string) ([]*cpu, error) {
56
145
// indexes (e.g. data[index[i], index[i+1]]).
57
146
// There should be len(indicies) - 1 CPUs
58
147
// 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 )
60
149
// Find each string that represents a CPU. These begin "processor".
61
150
for i := 1 ; i < len (indices ); i ++ {
62
151
start := indices [i - 1 ][0 ]
63
152
end := indices [i ][0 ]
64
153
// Parse the CPU entry, which should be between start/end.
65
- c , err := getCPU (data [start :end ])
154
+ c , err := newThread (data [start :end ])
66
155
if err != nil {
67
156
return nil , err
68
157
}
@@ -71,18 +160,25 @@ func getCPUSet(data string) ([]*cpu, error) {
71
160
return cpus , nil
72
161
}
73
162
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
+
74
170
// type cpu represents pertinent info about a cpu.
75
- type cpu struct {
171
+ type thread struct {
76
172
processorNumber int64 // the processor number of this CPU.
77
173
vendorID string // the vendorID of CPU (e.g. AuthenticAMD).
78
174
cpuFamily int64 // CPU family number (e.g. 6 for CascadeLake/Skylake).
79
175
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
81
177
bugs map [string ]struct {} // map of vulnerabilities parsed from the 'bugs' field.
82
178
}
83
179
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 ) {
86
182
processor , err := parseProcessor (data )
87
183
if err != nil {
88
184
return nil , err
@@ -103,6 +199,11 @@ func getCPU(data string) (*cpu, error) {
103
199
return nil , err
104
200
}
105
201
202
+ physicalID , err := parsePhysicalID (data )
203
+ if err != nil {
204
+ return nil , err
205
+ }
206
+
106
207
coreID , err := parseCoreID (data )
107
208
if err != nil {
108
209
return nil , err
@@ -113,16 +214,41 @@ func getCPU(data string) (*cpu, error) {
113
214
return nil , err
114
215
}
115
216
116
- return & cpu {
217
+ return & thread {
117
218
processorNumber : processor ,
118
219
vendorID : vendorID ,
119
220
cpuFamily : cpuFamily ,
120
221
model : model ,
121
- coreID : coreID ,
122
- bugs : bugs ,
222
+ id : cpuID {
223
+ physicalID : physicalID ,
224
+ coreID : coreID ,
225
+ },
226
+ bugs : bugs ,
123
227
}, nil
124
228
}
125
229
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
+
126
252
// List of pertinent side channel vulnerablilites.
127
253
// For mds, see: https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/mds.html.
128
254
var vulnerabilities = []string {
@@ -134,35 +260,46 @@ var vulnerabilities = []string{
134
260
}
135
261
136
262
// isVulnerable checks if a CPU is vulnerable to pertinent bugs.
137
- func (c * cpu ) isVulnerable () bool {
263
+ func (t * thread ) isVulnerable () bool {
138
264
for _ , bug := range vulnerabilities {
139
- if _ , ok := c .bugs [bug ]; ok {
265
+ if _ , ok := t .bugs [bug ]; ok {
140
266
return true
141
267
}
142
268
}
143
269
return false
144
270
}
145
271
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
+
146
283
// similarTo checks family/model/bugs fields for equality of two
147
284
// 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 {
150
287
return false
151
288
}
152
289
153
- if other .cpuFamily != c .cpuFamily {
290
+ if other .cpuFamily != t .cpuFamily {
154
291
return false
155
292
}
156
293
157
- if other .model != c .model {
294
+ if other .model != t .model {
158
295
return false
159
296
}
160
297
161
- if len (other .bugs ) != len (c .bugs ) {
298
+ if len (other .bugs ) != len (t .bugs ) {
162
299
return false
163
300
}
164
301
165
- for bug := range c .bugs {
302
+ for bug := range t .bugs {
166
303
if _ , ok := other .bugs [bug ]; ! ok {
167
304
return false
168
305
}
@@ -190,6 +327,11 @@ func parseModel(data string) (int64, error) {
190
327
return parseIntegerResult (data , modelKey )
191
328
}
192
329
330
+ // parsePhysicalID parses the physical id field.
331
+ func parsePhysicalID (data string ) (int64 , error ) {
332
+ return parseIntegerResult (data , physicalIDKey )
333
+ }
334
+
193
335
// parseCoreID parses the core id field.
194
336
func parseCoreID (data string ) (int64 , error ) {
195
337
return parseIntegerResult (data , coreIDKey )
0 commit comments