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