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  	if buildcfg.GOOS == "windows" && buildcfg.GOARCH == "386" {
   200  		return false
   201  	}
   202  
   203  	// AIX doesn't just work, and it's not worth fixing.
   204  	if buildcfg.GOOS == "aix" {
   205  		return false
   206  	}
   207  
   208  	return enableFIPS
   209  }
   210  
   211  // setFIPSType should be called every time s.Type is set or changed.
   212  // It changes the type to one of the FIPS type (for example, STEXT -> STEXTFIPS) if appropriate.
   213  func (s *LSym) setFIPSType(ctxt *Link) {
   214  	if !EnableFIPS() {
   215  		return
   216  	}
   217  
   218  	// External test packages are not in scope.
   219  	if strings.HasSuffix(ctxt.Pkgpath, "_test") {
   220  		return
   221  	}
   222  
   223  	if s.Attribute.Static() {
   224  		// Static (file-scoped) symbol does not have name prefix,
   225  		// but must be local to package; rely on whether package is FIPS.
   226  		if !ctxt.IsFIPS() {
   227  			return
   228  		}
   229  	} else {
   230  		// Name must begin with crypto/internal/fips140, then dot or slash.
   231  		// The quick check for 'c' before the string compare is probably overkill,
   232  		// but this function is called a fair amount, and we don't want to
   233  		// slow down all the non-FIPS compilations.
   234  		const prefix = "crypto/internal/fips140"
   235  		name := s.Name
   236  		if len(name) <= len(prefix) || (name[len(prefix)] != '.' && name[len(prefix)] != '/') || name[0] != 'c' || name[:len(prefix)] != prefix {
   237  			return
   238  		}
   239  
   240  		// Now we're at least handling a FIPS symbol.
   241  		// It's okay to be slower now, since this code only runs when compiling a few packages.
   242  		// Text symbols are always okay, since they can use PC-relative relocations,
   243  		// but some data symbols are not.
   244  		if s.Type != objabi.STEXT && s.Type != objabi.STEXTFIPS {
   245  			// Even in the crypto/internal/fips140 packages,
   246  			// we exclude various Go runtime metadata,
   247  			// so that it can be allowed to contain data relocations.
   248  			if strings.Contains(name, ".inittask") ||
   249  				strings.Contains(name, ".dict") ||
   250  				strings.Contains(name, ".typeAssert") ||
   251  				strings.HasSuffix(name, ".arginfo0") ||
   252  				strings.HasSuffix(name, ".arginfo1") ||
   253  				strings.HasSuffix(name, ".argliveinfo") ||
   254  				strings.HasSuffix(name, ".args_stackmap") ||
   255  				strings.HasSuffix(name, ".opendefer") ||
   256  				strings.HasSuffix(name, ".stkobj") ||
   257  				strings.HasSuffix(name, "·f") {
   258  				return
   259  			}
   260  
   261  			// This symbol is linknamed to go:fipsinfo,
   262  			// so we shouldn't see it, but skip it just in case.
   263  			if s.Name == "crypto/internal/fips140/check.linkinfo" {
   264  				return
   265  			}
   266  		}
   267  	}
   268  
   269  	// This is a FIPS symbol! Convert its type to FIPS.
   270  
   271  	// Allow hash-based bisect to override our decision.
   272  	if bisectFIPS != nil {
   273  		h := bisect.Hash(s.Name)
   274  		if bisectFIPS.ShouldPrint(h) {
   275  			fmt.Fprintf(os.Stderr, "%v %s (%v)\n", bisect.Marker(h), s.Name, s.Type)
   276  		}
   277  		if !bisectFIPS.ShouldEnable(h) {
   278  			return
   279  		}
   280  	}
   281  
   282  	switch s.Type {
   283  	case objabi.STEXT:
   284  		s.Type = objabi.STEXTFIPS
   285  	case objabi.SDATA:
   286  		s.Type = objabi.SDATAFIPS
   287  	case objabi.SRODATA:
   288  		s.Type = objabi.SRODATAFIPS
   289  	case objabi.SNOPTRDATA:
   290  		s.Type = objabi.SNOPTRDATAFIPS
   291  	}
   292  }
   293  
   294  // checkFIPSReloc should be called for every relocation applied to s.
   295  // It rejects absolute (non-PC-relative) address relocations when building
   296  // with go build -buildmode=pie (which triggers the compiler's -shared flag),
   297  // because those relocations will be applied before crypto/internal/fips140/check
   298  // can hash-verify the FIPS code+data, which will make the verification fail.
   299  func (s *LSym) checkFIPSReloc(ctxt *Link, rel Reloc) {
   300  	if !ctxt.Flag_shared {
   301  		// Writing a non-position-independent binary, so all the
   302  		// relocations will be applied at link time, before we
   303  		// calculate the expected hash. Anything goes.
   304  		return
   305  	}
   306  
   307  	// Pseudo-relocations don't show up in code or data and are fine.
   308  	switch rel.Type {
   309  	case objabi.R_INITORDER,
   310  		objabi.R_KEEP,
   311  		objabi.R_USEIFACE,
   312  		objabi.R_USEIFACEMETHOD,
   313  		objabi.R_USENAMEDMETHOD:
   314  		return
   315  	}
   316  
   317  	// Otherwise, any relocation we emit must be possible to handle
   318  	// in the linker, meaning it has to be a PC-relative relocation
   319  	// or a non-symbol relocation like a TLS relocation.
   320  
   321  	// There are no PC-relative or TLS relocations in data. All data relocations are bad.
   322  	if s.Type != objabi.STEXTFIPS {
   323  		ctxt.Diag("%s: invalid relocation %v in fips data (%v)", s, rel.Type, s.Type)
   324  		return
   325  	}
   326  
   327  	// In code, check that only PC-relative relocations are being used.
   328  	// See ../objabi/reloctype.go comments for descriptions.
   329  	switch rel.Type {
   330  	case objabi.R_ADDRARM64, // used with ADRP+ADD, so PC-relative
   331  		objabi.R_ADDRMIPS,  // used by adding to REGSB, so position-independent
   332  		objabi.R_ADDRMIPSU, // used by adding to REGSB, so position-independent
   333  		objabi.R_ADDRMIPSTLS,
   334  		objabi.R_ADDROFF,
   335  		objabi.R_ADDRPOWER_GOT,
   336  		objabi.R_ADDRPOWER_GOT_PCREL34,
   337  		objabi.R_ADDRPOWER_PCREL,
   338  		objabi.R_ADDRPOWER_TOCREL,
   339  		objabi.R_ADDRPOWER_TOCREL_DS,
   340  		objabi.R_ADDRPOWER_PCREL34,
   341  		objabi.R_ARM64_TLS_LE,
   342  		objabi.R_ARM64_TLS_IE,
   343  		objabi.R_ARM64_GOTPCREL,
   344  		objabi.R_ARM64_GOT,
   345  		objabi.R_ARM64_PCREL,
   346  		objabi.R_ARM64_PCREL_LDST8,
   347  		objabi.R_ARM64_PCREL_LDST16,
   348  		objabi.R_ARM64_PCREL_LDST32,
   349  		objabi.R_ARM64_PCREL_LDST64,
   350  		objabi.R_CALL,
   351  		objabi.R_CALLARM,
   352  		objabi.R_CALLARM64,
   353  		objabi.R_CALLIND,
   354  		objabi.R_CALLLOONG64,
   355  		objabi.R_CALLPOWER,
   356  		objabi.R_GOTPCREL,
   357  		objabi.R_LOONG64_ADDR_LO, // used with PC-relative load
   358  		objabi.R_LOONG64_ADDR_HI, // used with PC-relative load
   359  		objabi.R_LOONG64_TLS_LE_HI,
   360  		objabi.R_LOONG64_TLS_LE_LO,
   361  		objabi.R_LOONG64_TLS_IE_HI,
   362  		objabi.R_LOONG64_TLS_IE_LO,
   363  		objabi.R_LOONG64_GOT_HI,
   364  		objabi.R_LOONG64_GOT_LO,
   365  		objabi.R_JMP16LOONG64,
   366  		objabi.R_JMP21LOONG64,
   367  		objabi.R_JMPLOONG64,
   368  		objabi.R_PCREL,
   369  		objabi.R_PCRELDBL,
   370  		objabi.R_POWER_TLS_LE,
   371  		objabi.R_POWER_TLS_IE,
   372  		objabi.R_POWER_TLS,
   373  		objabi.R_POWER_TLS_IE_PCREL34,
   374  		objabi.R_POWER_TLS_LE_TPREL34,
   375  		objabi.R_RISCV_JAL,
   376  		objabi.R_RISCV_PCREL_ITYPE,
   377  		objabi.R_RISCV_PCREL_STYPE,
   378  		objabi.R_RISCV_TLS_IE,
   379  		objabi.R_RISCV_TLS_LE,
   380  		objabi.R_RISCV_GOT_HI20,
   381  		objabi.R_RISCV_GOT_PCREL_ITYPE,
   382  		objabi.R_RISCV_PCREL_HI20,
   383  		objabi.R_RISCV_PCREL_LO12_I,
   384  		objabi.R_RISCV_PCREL_LO12_S,
   385  		objabi.R_RISCV_BRANCH,
   386  		objabi.R_RISCV_RVC_BRANCH,
   387  		objabi.R_RISCV_RVC_JUMP,
   388  		objabi.R_TLS_IE,
   389  		objabi.R_TLS_LE,
   390  		objabi.R_WEAKADDROFF:
   391  		// ok
   392  		return
   393  
   394  	case objabi.R_ADDRPOWER,
   395  		objabi.R_ADDRPOWER_DS,
   396  		objabi.R_CALLMIPS,
   397  		objabi.R_JMPMIPS:
   398  		// NOT OK!
   399  		//
   400  		// These are all non-PC-relative but listed here to record that we
   401  		// looked at them and decided explicitly that they aren't okay.
   402  		// Don't add them to the list above.
   403  	}
   404  	ctxt.Diag("%s: invalid relocation %v in fips code", s, rel.Type)
   405  }
   406  

View as plain text