mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
Host libgcc.a (among other libraries) on loong64 systems may contain such relocs if built with the "medium" code model and/or linker relaxation, which is increasingly the case. Make the internal linker aware of these for cgo interopability going forward. While at it, fix some of the comments for the loong64-specific RelocTypes. Fixes #75562 Change-Id: I0810969dcd229c5131ef06b0f70f51d81a3be4cd Reviewed-on: https://go-review.googlesource.com/c/go/+/709717 Reviewed-by: abner chenc <chenguoqi@loongson.cn> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Mark Freeman <markfreeman@google.com> Reviewed-by: Meidan Li <limeidan@loongson.cn> Reviewed-by: Junyang Shao <shaojunyang@google.com>
407 lines
15 KiB
Go
407 lines
15 KiB
Go
// 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)
|
|
}
|