-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathfips140.go
410 lines (360 loc) · 14.8 KB
/
fips140.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
FIPS-140 Verification Support
# Overview
For FIPS-140 crypto certification, one of the requirements is that the
“cryptographic module” perform a power-on self-test that includes
verification of its code+data at startup, ostensibly to guard against
corruption. (Like most of FIPS, the actual value here is as questionable
as it is non-negotiable.) Specifically, at startup we need to compute
an HMAC-SHA256 of the cryptographic code+data and compare it against a
build-time HMAC-SHA256 that has been stored in the binary as well.
This obviously guards against accidental corruption only, not attacks.
We could compute an HMAC-SHA256 of the entire binary, but that's more
startup latency than we'd like. (At 500 MB/s, a large 50MB binary
would incur a 100ms hit.) Also, as we'll see, there are some
limitations imposed on the code+data being hashed, and it's nice to
restrict those to the actual cryptographic packages.
# FIPS Symbol Types
Since we're not hashing the whole binary, we need to record the parts
of the binary that contain FIPS code, specifically the part of the
binary corresponding to the crypto/internal/fips140 package subtree.
To do that, we create special symbol types STEXTFIPS, SRODATAFIPS,
SNOPTRDATAFIPS, and SDATAFIPS, which those packages use instead of
STEXT, SRODATA, SNOPTRDATA, and SDATA. The linker groups symbols by
their type, so that naturally makes the FIPS parts contiguous within a
given type. The linker then writes out in a special symbol the start
and end of each of these FIPS-specific sections, alongside the
expected HMAC-SHA256 of them. At startup, the crypto/internal/fips140/check
package has an init function that recomputes the hash and checks it
against the recorded expectation.
The first important functionality in this file, then, is converting
from the standard symbol types to the FIPS symbol types, in the code
that needs them. Every time an LSym.Type is set, code must call
[LSym.setFIPSType] to update the Type to a FIPS type if appropriate.
# Relocation Restrictions
Of course, for the hashes to match, the FIPS code+data written by the
linker has to match the FIPS code+data in memory at init time.
This means that there cannot be an load-time relocations that modify
the FIPS code+data. In a standard -buildmode=exe build, that's vacuously
true, since those binaries have no load-time relocations at all.
For a -buildmode=pie build, there's more to be done.
Specifically, we have to make sure that all the relocations needed are
position-independent, so that they can be applied a link time with no
load-time component. For the code segment (the STEXTFIPS symbols),
that means only using PC-relative relocations. For the data segment,
that means basically having no relocations at all. In particular,
there cannot be R_ADDR relocations.
For example, consider the compilation of code like the global variables:
var array = [...]int{10, 20, 30}
var slice = array[:]
The standard implementation of these globals is to fill out the array
values in an SDATA symbol at link time, and then also to fill out the
slice header at link time as {nil, 3, 3}, along with a relocation to
fill in the first word of the slice header with the pointer &array at
load time, once the address of array is known.
A similar issue happens with:
var slice = []int{10, 20, 30}
The compiler invents an anonymous array and then treats the code as in
the first example. In both cases, a load-time relocation applied
before the crypto/internal/fips140/check init function would invalidate
the hash. Instead, we disable the “link time initialization” optimizations
in the compiler (package staticinit) for the fips packages.
That way, the slice initialization is deferred to its own init function.
As long as the package in question imports crypto/internal/fips140/check,
the hash check will happen before the package's own init function
runs, and so the hash check will see the slice header written by the
linker, with a slice base pointer predictably nil instead of the
unpredictable &array address.
The details of disabling the static initialization appropriately are
left to the compiler (see ../../compile/internal/staticinit).
This file is only concerned with making sure that no hash-invalidating
relocations sneak into the object files. [LSym.checkFIPSReloc] is called
for every new relocation in a symbol in a FIPS package (as reported by
[Link.IsFIPS]) and rejects invalid relocations.
# FIPS and Non-FIPS Symbols
The cryptographic code+data must be included in the hash-verified
data. In general we accomplish that by putting all symbols from
crypto/internal/fips140/... packages into the hash-verified data.
But not all.
Note that wrapper code that layers a Go API atop the cryptographic
core is unverified. For example, crypto/internal/fips140/sha256 is part of
the FIPS module and verified but the crypto/sha256 package that wraps
it is outside the module and unverified. Also, runtime support like
the implementation of malloc and garbage collection is outside the
FIPS module. Again, only the core cryptographic code and data is in
scope for the verification.
By analogy with these cases, we treat function wrappers like foo·f
(the function pointer form of func foo) and runtime support data like
runtime type descriptors, generic dictionaries, stack maps, and
function argument data as being outside the FIPS module. That's
important because some of them need to be contiguous with other
non-FIPS data, and all of them include data relocations that would be
incompatible with the hash verification.
# Debugging
Bugs in the handling of FIPS symbols can be mysterious. It is very
helpful to narrow the bug down to a specific symbol that causes a
problem when treated as a FIPS symbol. Rather than work that out
manually, if “go test strings” is failing, then you can use
go install golang.org/x/tools/cmd/bisect@latest
bisect -compile=fips go test strings
to automatically bisect which symbol triggers the bug.
# Link-Time Hashing
The link-time hash preparation is out of scope for this file;
see ../../link/internal/ld/fips.go for those details.
*/
package obj
import (
"cmd/internal/objabi"
"fmt"
"internal/bisect"
"internal/buildcfg"
"log"
"os"
"strings"
)
const enableFIPS = true
// IsFIPS reports whether we are compiling one of the crypto/internal/fips140/... packages.
func (ctxt *Link) IsFIPS() bool {
if strings.HasSuffix(ctxt.Pkgpath, "_test") {
// External test packages are outside the FIPS hash scope.
// This allows them to use //go:embed, which would otherwise
// emit absolute relocations in the global data.
return false
}
return ctxt.Pkgpath == "crypto/internal/fips140" || strings.HasPrefix(ctxt.Pkgpath, "crypto/internal/fips140/")
}
// bisectFIPS controls bisect-based debugging of FIPS symbol assignment.
var bisectFIPS *bisect.Matcher
// SetFIPSDebugHash sets the bisect pattern for debugging FIPS changes.
// The compiler calls this with the pattern set by -d=fipshash=pattern,
// so that if FIPS symbol type conversions are causing problems,
// you can use 'bisect -compile fips go test strings' to identify exactly
// which symbol is not being handled correctly.
func SetFIPSDebugHash(pattern string) {
m, err := bisect.New(pattern)
if err != nil {
log.Fatal(err)
}
bisectFIPS = m
}
// EnableFIPS reports whether FIPS should be enabled at all
// on the current buildcfg GOOS and GOARCH.
func EnableFIPS() bool {
// WASM is out of scope; its binaries are too weird.
// I'm not even sure it can read its own code.
if buildcfg.GOARCH == "wasm" {
return false
}
// CL 214397 added -buildmode=pie to windows-386
// and made it the default, but the implementation is
// not a true position-independent executable.
// Instead, it writes tons of relocations into the executable
// and leaves the loader to apply them to update the text
// segment for the specific address where the code was loaded.
// It should instead pass -shared to the compiler to get true
// position-independent code, at which point FIPS verification
// would work fine. FIPS verification does work fine on -buildmode=exe,
// but -buildmode=pie is the default, so crypto/internal/fips140/check
// would fail during all.bash if we enabled FIPS here.
// Perhaps the default should be changed back to -buildmode=exe,
// after which we could remove this case, but until then,
// skip FIPS on windows-386.
//
// We don't know whether arm works, because it is too hard to get builder
// time to test it. Disable since it's not important right now.
if buildcfg.GOOS == "windows" {
switch buildcfg.GOARCH {
case "386", "arm":
return false
}
}
// AIX doesn't just work, and it's not worth fixing.
if buildcfg.GOOS == "aix" {
return false
}
return enableFIPS
}
// setFIPSType should be called every time s.Type is set or changed.
// It changes the type to one of the FIPS type (for example, STEXT -> STEXTFIPS) if appropriate.
func (s *LSym) setFIPSType(ctxt *Link) {
if !EnableFIPS() {
return
}
// External test packages are not in scope.
if strings.HasSuffix(ctxt.Pkgpath, "_test") {
return
}
if s.Attribute.Static() {
// Static (file-scoped) symbol does not have name prefix,
// but must be local to package; rely on whether package is FIPS.
if !ctxt.IsFIPS() {
return
}
} else {
// Name must begin with crypto/internal/fips140, then dot or slash.
// The quick check for 'c' before the string compare is probably overkill,
// but this function is called a fair amount, and we don't want to
// slow down all the non-FIPS compilations.
const prefix = "crypto/internal/fips140"
name := s.Name
if len(name) <= len(prefix) || (name[len(prefix)] != '.' && name[len(prefix)] != '/') || name[0] != 'c' || name[:len(prefix)] != prefix {
return
}
// Now we're at least handling a FIPS symbol.
// It's okay to be slower now, since this code only runs when compiling a few packages.
// Text symbols are always okay, since they can use PC-relative relocations,
// but some data symbols are not.
if s.Type != objabi.STEXT && s.Type != objabi.STEXTFIPS {
// Even in the crypto/internal/fips140 packages,
// we exclude various Go runtime metadata,
// so that it can be allowed to contain data relocations.
if strings.Contains(name, ".inittask") ||
strings.Contains(name, ".dict") ||
strings.Contains(name, ".typeAssert") ||
strings.HasSuffix(name, ".arginfo0") ||
strings.HasSuffix(name, ".arginfo1") ||
strings.HasSuffix(name, ".argliveinfo") ||
strings.HasSuffix(name, ".args_stackmap") ||
strings.HasSuffix(name, ".opendefer") ||
strings.HasSuffix(name, ".stkobj") ||
strings.HasSuffix(name, "·f") {
return
}
// This symbol is linknamed to go:fipsinfo,
// so we shouldn't see it, but skip it just in case.
if s.Name == "crypto/internal/fips140/check.linkinfo" {
return
}
}
}
// This is a FIPS symbol! Convert its type to FIPS.
// Allow hash-based bisect to override our decision.
if bisectFIPS != nil {
h := bisect.Hash(s.Name)
if bisectFIPS.ShouldPrint(h) {
fmt.Fprintf(os.Stderr, "%v %s (%v)\n", bisect.Marker(h), s.Name, s.Type)
}
if !bisectFIPS.ShouldEnable(h) {
return
}
}
switch s.Type {
case objabi.STEXT:
s.Type = objabi.STEXTFIPS
case objabi.SDATA:
s.Type = objabi.SDATAFIPS
case objabi.SRODATA:
s.Type = objabi.SRODATAFIPS
case objabi.SNOPTRDATA:
s.Type = objabi.SNOPTRDATAFIPS
}
}
// checkFIPSReloc should be called for every relocation applied to s.
// It rejects absolute (non-PC-relative) address relocations when building
// with go build -buildmode=pie (which triggers the compiler's -shared flag),
// because those relocations will be applied before crypto/internal/fips140/check
// can hash-verify the FIPS code+data, which will make the verification fail.
func (s *LSym) checkFIPSReloc(ctxt *Link, rel Reloc) {
if !ctxt.Flag_shared {
// Writing a non-position-independent binary, so all the
// relocations will be applied at link time, before we
// calculate the expected hash. Anything goes.
return
}
// Pseudo-relocations don't show up in code or data and are fine.
switch rel.Type {
case objabi.R_INITORDER,
objabi.R_KEEP,
objabi.R_USEIFACE,
objabi.R_USEIFACEMETHOD,
objabi.R_USENAMEDMETHOD:
return
}
// Otherwise, any relocation we emit must be possible to handle
// in the linker, meaning it has to be a PC-relative relocation
// or a non-symbol relocation like a TLS relocation.
// There are no PC-relative or TLS relocations in data. All data relocations are bad.
if s.Type != objabi.STEXTFIPS {
ctxt.Diag("%s: invalid relocation %v in fips data (%v)", s, rel.Type, s.Type)
return
}
// In code, check that only PC-relative relocations are being used.
// See ../objabi/reloctype.go comments for descriptions.
switch rel.Type {
case objabi.R_ADDRARM64, // used with ADRP+ADD, so PC-relative
objabi.R_ADDRMIPS, // used by adding to REGSB, so position-independent
objabi.R_ADDRMIPSU, // used by adding to REGSB, so position-independent
objabi.R_ADDRMIPSTLS,
objabi.R_ADDROFF,
objabi.R_ADDRPOWER_GOT,
objabi.R_ADDRPOWER_GOT_PCREL34,
objabi.R_ADDRPOWER_PCREL,
objabi.R_ADDRPOWER_TOCREL,
objabi.R_ADDRPOWER_TOCREL_DS,
objabi.R_ADDRPOWER_PCREL34,
objabi.R_ARM64_TLS_LE,
objabi.R_ARM64_TLS_IE,
objabi.R_ARM64_GOTPCREL,
objabi.R_ARM64_GOT,
objabi.R_ARM64_PCREL,
objabi.R_ARM64_PCREL_LDST8,
objabi.R_ARM64_PCREL_LDST16,
objabi.R_ARM64_PCREL_LDST32,
objabi.R_ARM64_PCREL_LDST64,
objabi.R_CALL,
objabi.R_CALLARM,
objabi.R_CALLARM64,
objabi.R_CALLIND,
objabi.R_CALLLOONG64,
objabi.R_CALLPOWER,
objabi.R_GOTPCREL,
objabi.R_LOONG64_ADDR_LO, // used with PC-relative load
objabi.R_LOONG64_ADDR_HI, // used with PC-relative load
objabi.R_LOONG64_TLS_LE_HI,
objabi.R_LOONG64_TLS_LE_LO,
objabi.R_LOONG64_TLS_IE_HI,
objabi.R_LOONG64_TLS_IE_LO,
objabi.R_LOONG64_GOT_HI,
objabi.R_LOONG64_GOT_LO,
objabi.R_JMP16LOONG64,
objabi.R_JMP21LOONG64,
objabi.R_JMPLOONG64,
objabi.R_PCREL,
objabi.R_PCRELDBL,
objabi.R_POWER_TLS_LE,
objabi.R_POWER_TLS_IE,
objabi.R_POWER_TLS,
objabi.R_POWER_TLS_IE_PCREL34,
objabi.R_POWER_TLS_LE_TPREL34,
objabi.R_RISCV_JAL,
objabi.R_RISCV_PCREL_ITYPE,
objabi.R_RISCV_PCREL_STYPE,
objabi.R_RISCV_TLS_IE,
objabi.R_RISCV_TLS_LE,
objabi.R_RISCV_GOT_HI20,
objabi.R_RISCV_PCREL_HI20,
objabi.R_RISCV_PCREL_LO12_I,
objabi.R_RISCV_PCREL_LO12_S,
objabi.R_RISCV_BRANCH,
objabi.R_RISCV_RVC_BRANCH,
objabi.R_RISCV_RVC_JUMP,
objabi.R_TLS_IE,
objabi.R_TLS_LE,
objabi.R_WEAKADDROFF:
// ok
return
case objabi.R_ADDRPOWER,
objabi.R_ADDRPOWER_DS,
objabi.R_CALLMIPS,
objabi.R_JMPMIPS:
// NOT OK!
//
// These are all non-PC-relative but listed here to record that we
// looked at them and decided explicitly that they aren't okay.
// Don't add them to the list above.
}
ctxt.Diag("%s: invalid relocation %v in fips code", s, rel.Type)
}