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  

View as plain text