| // 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. | |
| if buildcfg.GOOS == "windows" && buildcfg.GOARCH == "386" { | |
| 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_ADDR_PCREL20_S2, // used with PC-relative load | |
| objabi.R_LOONG64_CALL36, | |
| 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_GOT_PCREL_ITYPE, | |
| 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) | |
| } | |