Source file src/cmd/internal/obj/fips.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/fips 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/fips/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/fips/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/fips/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/fips/... 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/fips/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/fips/... packages.
   150  func (ctxt *Link) IsFIPS() bool {
   151  	return ctxt.Pkgpath == "crypto/internal/fips" || strings.HasPrefix(ctxt.Pkgpath, "crypto/internal/fips/")
   152  }
   153  
   154  // bisectFIPS controls bisect-based debugging of FIPS symbol assignment.
   155  var bisectFIPS *bisect.Matcher
   156  
   157  // SetFIPSDebugHash sets the bisect pattern for debugging FIPS changes.
   158  // The compiler calls this with the pattern set by -d=fipshash=pattern,
   159  // so that if FIPS symbol type conversions are causing problems,
   160  // you can use 'bisect -compile fips go test strings' to identify exactly
   161  // which symbol is not being handled correctly.
   162  func SetFIPSDebugHash(pattern string) {
   163  	m, err := bisect.New(pattern)
   164  	if err != nil {
   165  		log.Fatal(err)
   166  	}
   167  	bisectFIPS = m
   168  }
   169  
   170  // EnableFIPS reports whether FIPS should be enabled at all
   171  // on the current buildcfg GOOS and GOARCH.
   172  func EnableFIPS() bool {
   173  	// WASM is out of scope; its binaries are too weird.
   174  	// I'm not even sure it can read its own code.
   175  	if buildcfg.GOARCH == "wasm" {
   176  		return false
   177  	}
   178  
   179  	// CL 214397 added -buildmode=pie to windows-386
   180  	// and made it the default, but the implementation is
   181  	// not a true position-independent executable.
   182  	// Instead, it writes tons of relocations into the executable
   183  	// and leaves the loader to apply them to update the text
   184  	// segment for the specific address where the code was loaded.
   185  	// It should instead pass -shared to the compiler to get true
   186  	// position-independent code, at which point FIPS verification
   187  	// would work fine. FIPS verification does work fine on -buildmode=exe,
   188  	// but -buildmode=pie is the default, so crypto/internal/fips/check
   189  	// would fail during all.bash if we enabled FIPS here.
   190  	// Perhaps the default should be changed back to -buildmode=exe,
   191  	// after which we could remove this case, but until then,
   192  	// skip FIPS on windows-386.
   193  	//
   194  	// We don't know whether arm or arm64 works, because it is
   195  	// too hard to get builder time to test them. Disable since they
   196  	// are not important right now.
   197  	if buildcfg.GOOS == "windows" {
   198  		switch buildcfg.GOARCH {
   199  		case "386", "arm", "arm64":
   200  			return false
   201  		}
   202  	}
   203  
   204  	// AIX doesn't just work, and it's not worth fixing.
   205  	if buildcfg.GOOS == "aix" {
   206  		return false
   207  	}
   208  
   209  	return enableFIPS
   210  }
   211  
   212  // setFIPSType should be called every time s.Type is set or changed.
   213  // It changes the type to one of the FIPS type (for example, STEXT -> STEXTFIPS) if appropriate.
   214  func (s *LSym) setFIPSType(ctxt *Link) {
   215  	if !EnableFIPS() {
   216  		return
   217  	}
   218  
   219  	// Name must begin with crypto/internal/fips, then dot or slash.
   220  	// The quick check for 'c' before the string compare is probably overkill,
   221  	// but this function is called a fair amount, and we don't want to
   222  	// slow down all the non-FIPS compilations.
   223  	const prefix = "crypto/internal/fips"
   224  	name := s.Name
   225  	if len(name) <= len(prefix) || (name[len(prefix)] != '.' && name[len(prefix)] != '/') || name[0] != 'c' || name[:len(prefix)] != prefix {
   226  		return
   227  	}
   228  
   229  	// Now we're at least handling a FIPS symbol.
   230  	// It's okay to be slower now, since this code only runs when compiling a few packages.
   231  
   232  	// Even in the crypto/internal/fips packages,
   233  	// we exclude various Go runtime metadata,
   234  	// so that it can be allowed to contain data relocations.
   235  	if strings.Contains(name, ".init") ||
   236  		strings.Contains(name, ".dict") ||
   237  		strings.Contains(name, ".typeAssert") ||
   238  		strings.HasSuffix(name, ".arginfo0") ||
   239  		strings.HasSuffix(name, ".arginfo1") ||
   240  		strings.HasSuffix(name, ".argliveinfo") ||
   241  		strings.HasSuffix(name, ".args_stackmap") ||
   242  		strings.HasSuffix(name, ".opendefer") ||
   243  		strings.HasSuffix(name, ".stkobj") ||
   244  		strings.HasSuffix(name, "·f") {
   245  		return
   246  	}
   247  
   248  	// This symbol is linknamed to go:fipsinfo,
   249  	// so we shouldn't see it, but skip it just in case.
   250  	if s.Name == "crypto/internal/fips/check.linkinfo" {
   251  		return
   252  	}
   253  
   254  	// This is a FIPS symbol! Convert its type to FIPS.
   255  
   256  	// Allow hash-based bisect to override our decision.
   257  	if bisectFIPS != nil {
   258  		h := bisect.Hash(s.Name)
   259  		if bisectFIPS.ShouldPrint(h) {
   260  			fmt.Fprintf(os.Stderr, "%v %s (%v)\n", bisect.Marker(h), s.Name, s.Type)
   261  		}
   262  		if !bisectFIPS.ShouldEnable(h) {
   263  			return
   264  		}
   265  	}
   266  
   267  	switch s.Type {
   268  	case objabi.STEXT:
   269  		s.Type = objabi.STEXTFIPS
   270  	case objabi.SDATA:
   271  		s.Type = objabi.SDATAFIPS
   272  	case objabi.SRODATA:
   273  		s.Type = objabi.SRODATAFIPS
   274  	case objabi.SNOPTRDATA:
   275  		s.Type = objabi.SNOPTRDATAFIPS
   276  	}
   277  }
   278  
   279  // checkFIPSReloc should be called for every relocation applied to s.
   280  // It rejects absolute (non-PC-relative) address relocations when building
   281  // with go build -buildmode=pie (which triggers the compiler's -shared flag),
   282  // because those relocations will be applied before crypto/internal/fips/check
   283  // can hash-verify the FIPS code+data, which will make the verification fail.
   284  func (s *LSym) checkFIPSReloc(ctxt *Link, rel Reloc) {
   285  	if !ctxt.Flag_shared {
   286  		// Writing a non-position-independent binary, so all the
   287  		// relocations will be applied at link time, before we
   288  		// calculate the expected hash. Anything goes.
   289  		return
   290  	}
   291  
   292  	// Pseudo-relocations don't show up in code or data and are fine.
   293  	switch rel.Type {
   294  	case objabi.R_INITORDER,
   295  		objabi.R_KEEP,
   296  		objabi.R_USEIFACE,
   297  		objabi.R_USEIFACEMETHOD,
   298  		objabi.R_USENAMEDMETHOD:
   299  		return
   300  	}
   301  
   302  	// Otherwise, any relocation we emit must be possible to handle
   303  	// in the linker, meaning it has to be a PC-relative relocation
   304  	// or a non-symbol relocation like a TLS relocation.
   305  
   306  	// There are no PC-relative or TLS relocations in data. All data relocations are bad.
   307  	if s.Type != objabi.STEXTFIPS {
   308  		ctxt.Diag("%s: invalid relocation %v in fips data (%v)", s, rel.Type, s.Type)
   309  		return
   310  	}
   311  
   312  	// In code, check that only PC-relative relocations are being used.
   313  	// See ../objabi/reloctype.go comments for descriptions.
   314  	switch rel.Type {
   315  	case objabi.R_ADDRARM64, // used with ADRP+ADD, so PC-relative
   316  		objabi.R_ADDRMIPS,  // used by adding to REGSB, so position-independent
   317  		objabi.R_ADDRMIPSU, // used by adding to REGSB, so position-independent
   318  		objabi.R_ADDRMIPSTLS,
   319  		objabi.R_ADDROFF,
   320  		objabi.R_ADDRPOWER_GOT,
   321  		objabi.R_ADDRPOWER_GOT_PCREL34,
   322  		objabi.R_ADDRPOWER_PCREL,
   323  		objabi.R_ADDRPOWER_TOCREL,
   324  		objabi.R_ADDRPOWER_TOCREL_DS,
   325  		objabi.R_ADDRPOWER_PCREL34,
   326  		objabi.R_ARM64_TLS_LE,
   327  		objabi.R_ARM64_TLS_IE,
   328  		objabi.R_ARM64_GOTPCREL,
   329  		objabi.R_ARM64_GOT,
   330  		objabi.R_ARM64_PCREL,
   331  		objabi.R_ARM64_PCREL_LDST8,
   332  		objabi.R_ARM64_PCREL_LDST16,
   333  		objabi.R_ARM64_PCREL_LDST32,
   334  		objabi.R_ARM64_PCREL_LDST64,
   335  		objabi.R_CALL,
   336  		objabi.R_CALLARM,
   337  		objabi.R_CALLARM64,
   338  		objabi.R_CALLIND,
   339  		objabi.R_CALLLOONG64,
   340  		objabi.R_CALLPOWER,
   341  		objabi.R_GOTPCREL,
   342  		objabi.R_LOONG64_ADDR_LO, // used with PC-relative load
   343  		objabi.R_LOONG64_ADDR_HI, // used with PC-relative load
   344  		objabi.R_LOONG64_TLS_LE_HI,
   345  		objabi.R_LOONG64_TLS_LE_LO,
   346  		objabi.R_LOONG64_TLS_IE_HI,
   347  		objabi.R_LOONG64_TLS_IE_LO,
   348  		objabi.R_LOONG64_GOT_HI,
   349  		objabi.R_LOONG64_GOT_LO,
   350  		objabi.R_JMP16LOONG64,
   351  		objabi.R_JMP21LOONG64,
   352  		objabi.R_JMPLOONG64,
   353  		objabi.R_PCREL,
   354  		objabi.R_PCRELDBL,
   355  		objabi.R_POWER_TLS_LE,
   356  		objabi.R_POWER_TLS_IE,
   357  		objabi.R_POWER_TLS,
   358  		objabi.R_POWER_TLS_IE_PCREL34,
   359  		objabi.R_POWER_TLS_LE_TPREL34,
   360  		objabi.R_RISCV_JAL,
   361  		objabi.R_RISCV_PCREL_ITYPE,
   362  		objabi.R_RISCV_PCREL_STYPE,
   363  		objabi.R_RISCV_TLS_IE,
   364  		objabi.R_RISCV_TLS_LE,
   365  		objabi.R_RISCV_GOT_HI20,
   366  		objabi.R_RISCV_PCREL_HI20,
   367  		objabi.R_RISCV_PCREL_LO12_I,
   368  		objabi.R_RISCV_PCREL_LO12_S,
   369  		objabi.R_RISCV_BRANCH,
   370  		objabi.R_RISCV_RVC_BRANCH,
   371  		objabi.R_RISCV_RVC_JUMP,
   372  		objabi.R_TLS_IE,
   373  		objabi.R_TLS_LE,
   374  		objabi.R_WEAKADDROFF:
   375  		// ok
   376  		return
   377  
   378  	case objabi.R_ADDRPOWER,
   379  		objabi.R_ADDRPOWER_DS,
   380  		objabi.R_CALLMIPS,
   381  		objabi.R_JMPMIPS:
   382  		// NOT OK!
   383  		//
   384  		// These are all non-PC-relative but listed here to record that we
   385  		// looked at them and decided explicitly that they aren't okay.
   386  		// Don't add them to the list above.
   387  	}
   388  	ctxt.Diag("%s: invalid relocation %v in fips code", s, rel.Type)
   389  }
   390  

View as plain text