]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile, cmd/link: add FIPS verification support
authorRuss Cox <rsc@golang.org>
Tue, 5 Nov 2024 18:51:32 +0000 (13:51 -0500)
committerGopher Robot <gobot@golang.org>
Wed, 13 Nov 2024 01:25:15 +0000 (01:25 +0000)
For FIPS init-time code+data verification, we need to arrange to
put the FIPS symbols into contiguous regions of the executable
and then record those sections along with the expected checksum.

The cmd/internal/obj changes identify the FIPS symbols and give
them distinguished types, which the linker then places in contiguous
regions. The linker also writes out information to use at run time
to find the FIPS sections, along with the expected hash.

See cmd/internal/obj/fips.go and cmd/link/internal/ld/fips.go
for more details.

The code is disabled in this commit.
CL 625998 and 625999 adds tests.
CL 626000 enables the code.

For #69536.

Change-Id: I48da6db94bc0bea7428c43d4abcf999527bccfcd
Reviewed-on: https://go-review.googlesource.com/c/go/+/625997
Auto-Submit: Russ Cox <rsc@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>

17 files changed:
src/cmd/compile/internal/base/debug.go
src/cmd/compile/internal/base/flag.go
src/cmd/compile/internal/staticinit/sched.go
src/cmd/compile/internal/walk/complit.go
src/cmd/compile/internal/walk/order.go
src/cmd/internal/obj/data.go
src/cmd/internal/obj/fips.go [new file with mode: 0644]
src/cmd/internal/obj/plist.go
src/cmd/internal/obj/sym.go
src/cmd/internal/objfile/goobj.go
src/cmd/link/internal/ld/data.go
src/cmd/link/internal/ld/elf.go
src/cmd/link/internal/ld/fips.go [new file with mode: 0644]
src/cmd/link/internal/ld/lib.go
src/cmd/link/internal/ld/main.go
src/cmd/link/internal/ld/symtab.go
src/cmd/link/internal/loader/loader.go

index 05da3efe484d5718781d2b26812d5d65ab877e21..d42e11b2fad881a992073633a9de090dc526cb73 100644 (file)
@@ -30,6 +30,7 @@ type DebugFlags struct {
        DwarfInl              int    `help:"print information about DWARF inlined function creation"`
        EscapeMutationsCalls  int    `help:"print extra escape analysis diagnostics about mutations and calls" concurrent:"ok"`
        Export                int    `help:"print export data"`
+       FIPSHash              string `help:"hash value for FIPS debugging" concurrent:"ok"`
        Fmahash               string `help:"hash value for use in debugging platform-dependent multiply-add use" concurrent:"ok"`
        GCAdjust              int    `help:"log adjustments to GOGC" concurrent:"ok"`
        GCCheck               int    `help:"check heap/gc use by compiler" concurrent:"ok"`
index b296f3666cf4752c06466dba4da787e69201f6ed..31ea8622b924c1c25333179213752b09787766aa 100644 (file)
@@ -206,6 +206,7 @@ func ParseFlags() {
        if Debug.Gossahash != "" {
                hashDebug = NewHashDebug("gossahash", Debug.Gossahash, nil)
        }
+       obj.SetFIPSDebugHash(Debug.FIPSHash)
 
        // Compute whether we're compiling the runtime from the package path. Test
        // code can also use the flag to set this explicitly.
index 0e2f7119c6278c6a5c28219538f41233ddf7c056..e013823ee71267d352029ff1b98ba195eb0b888c 100644 (file)
@@ -279,6 +279,14 @@ func (s *Schedule) staticcopy(l *ir.Name, loff int64, rn *ir.Name, typ *types.Ty
 }
 
 func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Type) bool {
+       // If we're building for FIPS, avoid global data relocations
+       // by treating all address-of operations as non-static.
+       // See ../../../internal/obj/fips.go for more context.
+       // We do this even in non-PIE mode to avoid generating
+       // static temporaries that would go into SRODATAFIPS
+       // but need relocations. We can't handle that in the verification.
+       disableGlobalAddrs := base.Ctxt.IsFIPS()
+
        if r == nil {
                // No explicit initialization value. Either zero or supplied
                // externally.
@@ -304,10 +312,16 @@ func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Ty
 
        switch r.Op() {
        case ir.ONAME:
+               if disableGlobalAddrs {
+                       return false
+               }
                r := r.(*ir.Name)
                return s.staticcopy(l, loff, r, typ)
 
        case ir.OMETHEXPR:
+               if disableGlobalAddrs {
+                       return false
+               }
                r := r.(*ir.SelectorExpr)
                return s.staticcopy(l, loff, r.FuncName(), typ)
 
@@ -322,6 +336,9 @@ func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Ty
                return true
 
        case ir.OADDR:
+               if disableGlobalAddrs {
+                       return false
+               }
                r := r.(*ir.AddrExpr)
                if name, offset, ok := StaticLoc(r.X); ok && name.Class == ir.PEXTERN {
                        staticdata.InitAddrOffset(l, loff, name.Linksym(), offset)
@@ -330,6 +347,9 @@ func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Ty
                fallthrough
 
        case ir.OPTRLIT:
+               if disableGlobalAddrs {
+                       return false
+               }
                r := r.(*ir.AddrExpr)
                switch r.X.Op() {
                case ir.OARRAYLIT, ir.OSLICELIT, ir.OMAPLIT, ir.OSTRUCTLIT:
@@ -346,6 +366,9 @@ func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Ty
                //dump("not static ptrlit", r);
 
        case ir.OSTR2BYTES:
+               if disableGlobalAddrs {
+                       return false
+               }
                r := r.(*ir.ConvExpr)
                if l.Class == ir.PEXTERN && r.X.Op() == ir.OLITERAL {
                        sval := ir.StringVal(r.X)
@@ -354,6 +377,9 @@ func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Ty
                }
 
        case ir.OSLICELIT:
+               if disableGlobalAddrs {
+                       return false
+               }
                r := r.(*ir.CompLitExpr)
                s.initplan(r)
                // Init slice.
@@ -374,7 +400,7 @@ func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Ty
                p := s.Plans[r]
                for i := range p.E {
                        e := &p.E[i]
-                       if e.Expr.Op() == ir.OLITERAL || e.Expr.Op() == ir.ONIL {
+                       if e.Expr.Op() == ir.OLITERAL && !disableGlobalAddrs || e.Expr.Op() == ir.ONIL {
                                staticdata.InitConst(l, loff+e.Xoffset, e.Expr, int(e.Expr.Type().Size()))
                                continue
                        }
@@ -388,6 +414,9 @@ func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Ty
                break
 
        case ir.OCLOSURE:
+               if disableGlobalAddrs {
+                       return false
+               }
                r := r.(*ir.ClosureExpr)
                if !r.Func.IsClosure() {
                        if base.Debug.Closure > 0 {
@@ -405,6 +434,10 @@ func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Ty
                // This logic is mirrored in isStaticCompositeLiteral.
                // If you change something here, change it there, and vice versa.
 
+               if disableGlobalAddrs {
+                       return false
+               }
+
                // Determine the underlying concrete type and value we are converting from.
                r := r.(*ir.ConvExpr)
                val := ir.Node(r)
@@ -460,6 +493,9 @@ func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Ty
                return true
 
        case ir.OINLCALL:
+               if disableGlobalAddrs {
+                       return false
+               }
                r := r.(*ir.InlinedCallExpr)
                return s.staticAssignInlinedCall(l, loff, r, typ)
        }
@@ -728,10 +764,12 @@ func (s *Schedule) staticAssignInlinedCall(l *ir.Name, loff int64, call *ir.Inli
 var statuniqgen int // name generator for static temps
 
 // StaticName returns a name backed by a (writable) static data symbol.
-// Use readonlystaticname for read-only node.
 func StaticName(t *types.Type) *ir.Name {
        // Don't use LookupNum; it interns the resulting string, but these are all unique.
-       sym := typecheck.Lookup(fmt.Sprintf("%s%d", obj.StaticNamePref, statuniqgen))
+       sym := typecheck.Lookup(fmt.Sprintf("%s%d", obj.StaticNamePrefix, statuniqgen))
+       if sym.Name == ".stmp_0" && sym.Pkg.Path == "crypto/internal/fips/check" {
+               panic("bad")
+       }
        statuniqgen++
 
        n := ir.NewNameAt(base.Pos, sym, t)
index cfdc8becfe1154864461b269b0e95d5cb3c7750e..70750ab0373b66c9ccb7e38c917c29d243379b3f 100644 (file)
@@ -153,7 +153,10 @@ func isStaticCompositeLiteral(n ir.Node) bool {
        case ir.OLITERAL, ir.ONIL:
                return true
        case ir.OCONVIFACE:
-               // See staticassign's OCONVIFACE case for comments.
+               // See staticinit.Schedule.StaticAssign's OCONVIFACE case for comments.
+               if base.Ctxt.IsFIPS() && base.Ctxt.Flag_shared {
+                       return false
+               }
                n := n.(*ir.ConvExpr)
                val := ir.Node(n)
                for val.Op() == ir.OCONVIFACE {
index 896088901e9acbfd8c77dedcdd19967c7a1b0ff3..613edf497b1be0b3e251ef95e13f2489b396b1b6 100644 (file)
@@ -220,7 +220,12 @@ func (o *orderState) safeExpr(n ir.Node) ir.Node {
 //
 //     n.Left = o.addrTemp(n.Left)
 func (o *orderState) addrTemp(n ir.Node) ir.Node {
-       if n.Op() == ir.OLITERAL || n.Op() == ir.ONIL {
+       // Note: Avoid addrTemp with static assignment for literal strings
+       // when compiling FIPS packages.
+       // The problem is that panic("foo") ends up creating a static RODATA temp
+       // for the implicit conversion of "foo" to any, and we can't handle
+       // the relocations in that temp.
+       if n.Op() == ir.ONIL || (n.Op() == ir.OLITERAL && !base.Ctxt.IsFIPS()) {
                // TODO: expand this to all static composite literal nodes?
                n = typecheck.DefaultLit(n, nil)
                types.CalcSize(n.Type())
index 361ea05a0fa2b6f8d9c9cfcd7992abf7b054d8b2..fb6edd605f7d15b632346e034c4c6b52f7494c7a 100644 (file)
@@ -71,8 +71,10 @@ func (s *LSym) prepwrite(ctxt *Link, off int64, siz int) {
        switch s.Type {
        case objabi.Sxxx, objabi.SBSS:
                s.Type = objabi.SDATA
+               s.setFIPSType(ctxt)
        case objabi.SNOPTRBSS:
                s.Type = objabi.SNOPTRDATA
+               s.setFIPSType(ctxt)
        case objabi.STLSBSS:
                ctxt.Diag("cannot supply data for %v var %v", s.Type, s.Name)
        }
@@ -203,5 +205,8 @@ func (s *LSym) WriteBytes(ctxt *Link, off int64, b []byte) int64 {
 
 // AddRel adds the relocation rel to s.
 func (s *LSym) AddRel(ctxt *Link, rel Reloc) {
+       if s.Type.IsFIPS() {
+               s.checkFIPSReloc(ctxt, rel)
+       }
        s.R = append(s.R, rel)
 }
diff --git a/src/cmd/internal/obj/fips.go b/src/cmd/internal/obj/fips.go
new file mode 100644 (file)
index 0000000..11028ce
--- /dev/null
@@ -0,0 +1,383 @@
+// 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/fips 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/fips/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/fips/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/fips/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/fips/... 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/fips/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 = false
+
+// IsFIPS reports whether we are compiling one of the crypto/internal/fips/... packages.
+func (ctxt *Link) IsFIPS() bool {
+       return ctxt.Pkgpath == "crypto/internal/fips" || strings.HasPrefix(ctxt.Pkgpath, "crypto/internal/fips/")
+}
+
+// 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/fips/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.
+       //
+       // We don't know whether arm or arm64 works, because it is
+       // too hard to get builder time to test them. Disable since they
+       // are not important right now.
+       if buildcfg.GOOS == "windows" {
+               switch buildcfg.GOARCH {
+               case "386", "arm", "arm64":
+                       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
+       }
+
+       // Name must begin with crypto/internal/fips, 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/fips"
+       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.
+
+       // Even in the crypto/internal/fips packages,
+       // we exclude various Go runtime metadata,
+       // so that it can be allowed to contain data relocations.
+       if strings.Contains(name, ".init") ||
+               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/fips/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/fips/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_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_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_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)
+}
index 4d4e7eb94b94bd789b545b9d7af889eb7f5d0384..698e5ace9ccca82bdec797abb7e00fd0ca014a91 100644 (file)
@@ -213,6 +213,7 @@ func (ctxt *Link) InitTextSym(s *LSym, flag int, start src.XPos) {
        s.Set(AttrNoFrame, flag&NOFRAME != 0)
        s.Set(AttrPkgInit, flag&PKGINIT != 0)
        s.Type = objabi.STEXT
+       s.setFIPSType(ctxt)
        ctxt.Text = append(ctxt.Text, s)
 
        // Set up DWARF entries for s
@@ -258,6 +259,7 @@ func (ctxt *Link) GloblPos(s *LSym, size int64, flag int, pos src.XPos) {
        } else if flag&TLSBSS != 0 {
                s.Type = objabi.STLSBSS
        }
+       s.setFIPSType(ctxt)
 }
 
 // EmitEntryLiveness generates PCDATA Progs after p to switch to the
index 472ca9eee6eacdfd0d20910616229c5dc9f00fce..4feccf54f6bc2a4ee28db42311e48e0007979232 100644 (file)
@@ -378,10 +378,10 @@ func isNonPkgSym(ctxt *Link, s *LSym) bool {
        return false
 }
 
-// StaticNamePref is the prefix the front end applies to static temporary
+// StaticNamePrefix is the prefix the front end applies to static temporary
 // variables. When turned into LSyms, these can be tagged as static so
 // as to avoid inserting them into the linker's name lookup tables.
-const StaticNamePref = ".stmp_"
+const StaticNamePrefix = ".stmp_"
 
 type traverseFlag uint32
 
index a0a2a1799b39e7193354934c9830ee345135b5ae..e8b8b522516b9f8df4bf38aeef8dd43a605c8cd9 100644 (file)
@@ -164,11 +164,12 @@ func (f *goobjFile) symbols() ([]Sym, error) {
                typ := objabi.SymKind(osym.Type())
                var code rune = '?'
                switch typ {
-               case objabi.STEXT:
+               case objabi.STEXT, objabi.STEXTFIPS:
                        code = 'T'
-               case objabi.SRODATA:
+               case objabi.SRODATA, objabi.SRODATAFIPS:
                        code = 'R'
-               case objabi.SNOPTRDATA, objabi.SDATA:
+               case objabi.SNOPTRDATA, objabi.SNOPTRDATAFIPS,
+                       objabi.SDATA, objabi.SDATAFIPS:
                        code = 'D'
                case objabi.SBSS, objabi.SNOPTRBSS, objabi.STLSBSS:
                        code = 'B'
index 31a1d4f160e50bf2a075b5511cf2648cbecb1417..a23e87d326d5c767d2abdc3f27c0a727f13c9fac 100644 (file)
@@ -1078,6 +1078,7 @@ func writeBlock(ctxt *Link, out *OutBuf, ldr *loader.Loader, syms []loader.Sym,
        // is the virtual address. DWARF compression changes file sizes,
        // so dwarfcompress will fix this up later if necessary.
        eaddr := addr + size
+       var prev loader.Sym
        for _, s := range syms {
                if ldr.AttrSubSymbol(s) {
                        continue
@@ -1087,9 +1088,11 @@ func writeBlock(ctxt *Link, out *OutBuf, ldr *loader.Loader, syms []loader.Sym,
                        break
                }
                if val < addr {
-                       ldr.Errorf(s, "phase error: addr=%#x but val=%#x sym=%s type=%v sect=%v sect.addr=%#x", addr, val, ldr.SymName(s), ldr.SymType(s), ldr.SymSect(s).Name, ldr.SymSect(s).Vaddr)
+                       ldr.Errorf(s, "phase error: addr=%#x but val=%#x sym=%s type=%v sect=%v sect.addr=%#x prev=%s", addr, val, ldr.SymName(s), ldr.SymType(s), ldr.SymSect(s).Name, ldr.SymSect(s).Vaddr, ldr.SymName(prev))
+                       panic("PHASE")
                        errorexit()
                }
+               prev = s
                if addr < val {
                        out.WriteStringPad("", int(val-addr), pad)
                        addr = val
@@ -1510,6 +1513,9 @@ func (state *dodataState) makeRelroForSharedLib(target *Link) {
                                isRelro = false
                        }
                        if isRelro {
+                               if symnrelro == sym.Sxxx {
+                                       state.ctxt.Errorf(s, "cannot contain relocations (type %v)", symnro)
+                               }
                                state.setSymType(s, symnrelro)
                                if outer := ldr.OuterSym(s); outer != 0 {
                                        state.setSymType(outer, symnrelro)
@@ -1846,6 +1852,7 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
        // Writable data sections that do not need any specialized handling.
        writable := []sym.SymKind{
                sym.SBUILDINFO,
+               sym.SFIPSINFO,
                sym.SELFSECT,
                sym.SMACHO,
                sym.SMACHOGOT,
@@ -1866,6 +1873,11 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
        ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.noptrdata", 0), sect)
        ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.enoptrdata", 0), sect)
 
+       state.assignToSection(sect, sym.SNOPTRDATAFIPSSTART, sym.SDATA)
+       state.assignToSection(sect, sym.SNOPTRDATAFIPS, sym.SDATA)
+       state.assignToSection(sect, sym.SNOPTRDATAFIPSEND, sym.SDATA)
+       state.assignToSection(sect, sym.SNOPTRDATAEND, sym.SDATA)
+
        hasinitarr := ctxt.linkShared
 
        /* shared library initializer */
@@ -1888,6 +1900,12 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
        sect = state.allocateNamedSectionAndAssignSyms(&Segdata, ".data", sym.SDATA, sym.SDATA, 06)
        ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.data", 0), sect)
        ldr.SetSymSect(ldr.LookupOrCreateSym("runtime.edata", 0), sect)
+
+       state.assignToSection(sect, sym.SDATAFIPSSTART, sym.SDATA)
+       state.assignToSection(sect, sym.SDATAFIPS, sym.SDATA)
+       state.assignToSection(sect, sym.SDATAFIPSEND, sym.SDATA)
+       state.assignToSection(sect, sym.SDATAEND, sym.SDATA)
+
        dataGcEnd := state.datsize - int64(sect.Vaddr)
 
        // On AIX, TOC entries must be the last of .data
@@ -2093,6 +2111,9 @@ func (state *dodataState) allocateDataSections(ctxt *Link) {
                        }
 
                        symn := sym.RelROMap[symnro]
+                       if symn == sym.Sxxx {
+                               continue
+                       }
                        symnStartValue := state.datsize
                        if len(state.data[symn]) != 0 {
                                symnStartValue = aligndatsize(state, symnStartValue, state.data[symn][0])
@@ -2433,8 +2454,15 @@ func (ctxt *Link) textaddress() {
                })
        }
 
+       // Sort the text symbols by type, so that FIPS symbols are
+       // gathered together, with the FIPS start and end symbols
+       // bracketing them , even if we've randomized the overall order.
+       sort.SliceStable(ctxt.Textp, func(i, j int) bool {
+               return ldr.SymType(ctxt.Textp[i]) < ldr.SymType(ctxt.Textp[j])
+       })
+
        text := ctxt.xdefine("runtime.text", sym.STEXT, 0)
-       etext := ctxt.xdefine("runtime.etext", sym.STEXT, 0)
+       etext := ctxt.xdefine("runtime.etext", sym.STEXTEND, 0)
        ldr.SetSymSect(text, sect)
        if ctxt.IsAIX() && ctxt.IsExternal() {
                // Setting runtime.text has a real symbol prevents ld to
@@ -2970,11 +2998,11 @@ func (ctxt *Link) address() []*sym.Segment {
        ctxt.defineInternal("runtime.functab", sym.SRODATA)
        ctxt.xdefine("runtime.epclntab", sym.SRODATA, int64(pclntab.Vaddr+pclntab.Length))
        ctxt.xdefine("runtime.noptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr))
-       ctxt.xdefine("runtime.enoptrdata", sym.SNOPTRDATA, int64(noptr.Vaddr+noptr.Length))
+       ctxt.xdefine("runtime.enoptrdata", sym.SNOPTRDATAEND, int64(noptr.Vaddr+noptr.Length))
        ctxt.xdefine("runtime.bss", sym.SBSS, int64(bss.Vaddr))
        ctxt.xdefine("runtime.ebss", sym.SBSS, int64(bss.Vaddr+bss.Length))
        ctxt.xdefine("runtime.data", sym.SDATA, int64(data.Vaddr))
-       ctxt.xdefine("runtime.edata", sym.SDATA, int64(data.Vaddr+data.Length))
+       ctxt.xdefine("runtime.edata", sym.SDATAEND, int64(data.Vaddr+data.Length))
        ctxt.xdefine("runtime.noptrbss", sym.SNOPTRBSS, int64(noptrbss.Vaddr))
        ctxt.xdefine("runtime.enoptrbss", sym.SNOPTRBSS, int64(noptrbss.Vaddr+noptrbss.Length))
        ctxt.xdefine("runtime.covctrs", sym.SCOVERAGE_COUNTER, int64(noptrbss.Vaddr+covCounterDataStartOff))
index 3a418d3b61ddf2c07b269ee11f2176ed0979e932..e6a525198fae25f31285abbc6153ea9b041bfaa6 100644 (file)
@@ -1437,6 +1437,7 @@ func (ctxt *Link) doelf() {
        shstrtabAddstring(".noptrbss")
        shstrtabAddstring(".go.fuzzcntrs")
        shstrtabAddstring(".go.buildinfo")
+       shstrtabAddstring(".go.fipsinfo")
        if ctxt.IsMIPS() {
                shstrtabAddstring(".MIPS.abiflags")
                shstrtabAddstring(".gnu.attributes")
@@ -1494,6 +1495,7 @@ func (ctxt *Link) doelf() {
                        shstrtabAddstring(elfRelType + ".data.rel.ro")
                }
                shstrtabAddstring(elfRelType + ".go.buildinfo")
+               shstrtabAddstring(elfRelType + ".go.fipsinfo")
                if ctxt.IsMIPS() {
                        shstrtabAddstring(elfRelType + ".MIPS.abiflags")
                        shstrtabAddstring(elfRelType + ".gnu.attributes")
diff --git a/src/cmd/link/internal/ld/fips.go b/src/cmd/link/internal/ld/fips.go
new file mode 100644 (file)
index 0000000..8223da4
--- /dev/null
@@ -0,0 +1,599 @@
+// 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
+
+See ../../../internal/obj/fips.go for a basic overview.
+This file is concerned with computing the hash of the FIPS code+data.
+Package obj has taken care of marking the FIPS symbols with the
+special types STEXTFIPS, SRODATAFIPS, SNOPTRDATAFIPS, and SDATAFIPS.
+
+# FIPS Symbol Layout
+
+The first order of business is collecting the FIPS symbols into
+contiguous sections of the final binary and identifying the start and
+end of those sections. The linker already tracks the start and end of
+the text section as runtime.text and runtime.etext, and similarly for
+other sections, but the implementation of those symbols is tricky and
+platform-specific. The problem is that they are zero-length
+pseudo-symbols that share addresses with other symbols, which makes
+everything harder. For the FIPS sections, we avoid that subtlety by
+defining actual non-zero-length symbols bracketing each section and
+use those symbols as the boundaries.
+
+Specifically, we define a 1-byte symbol go:textfipsstart of type
+STEXTFIPSSTART and a 1-byte symbol go:textfipsend of type STEXTFIPSEND,
+and we place those two symbols immediately before and after the
+STEXTFIPS symbols. We do the same for SRODATAFIPS, SNOPTRDATAFIPS,
+and SDATAFIPS. Because the symbols are real (but otherwise unused) data,
+they can be treated as normal symbols for symbol table purposes and
+don't need the same kind of special handling that runtime.text and
+friends do.
+
+Note that treating the FIPS text as starting at &go:textfipsstart and
+ending at &go:textfipsend means that go:textfipsstart is included in
+the verified data while go:textfipsend is not. That's fine: they are
+only framing and neither strictly needs to be in the hash.
+
+The new special symbols are created by [loadfips].
+
+# FIPS Info Layout
+
+Having collated the FIPS symbols, we need to compute the hash
+and then leave both the expected hash and the FIPS address ranges
+for the run-time check in crypto/internal/fips/check.
+We do that by creating a special symbol named go:fipsinfo of the form
+
+       struct {
+               sum   [32]byte
+               self  uintptr // points to start of struct
+               sects [4]struct{
+                       start uintptr
+                       end   uintptr
+               }
+       }
+
+The crypto/internal/fips/check uses linkname to access this symbol,
+which is of course not included in the hash.
+
+# FIPS Info Calculation
+
+When using internal linking, [asmbfips] runs after writing the output
+binary but before code-signing it. It reads the relevant sections
+back from the output file, hashes them, and then writes the go:fipsinfo
+content into the output file.
+
+When using external linking, especially with -buildmode=pie, we cannot
+predict the specific PLT index references that the linker will insert
+into the FIPS code sections, so we must read the final linked executable
+after external linking, compute the hash, and then write it back to the
+executable in the go:fipsinfo sum field. [hostlinkfips] does this.
+It finds go:fipsinfo easily because that symbol is given its own section
+(.go.fipsinfo on ELF, __go_fipsinfo on Mach-O), and then it can use the
+sections field to find the relevant parts of the executable, hash them,
+and fill in sum.
+
+Both [asmbfips] and [hostlinkfips] need the same hash calculation code.
+The [fipsObj] type provides that calculation.
+
+# Debugging
+
+It is of course impossible to debug a mismatched hash directly:
+two random 32-byte strings differ. For debugging, the linker flag
+-fipso can be set to the name of a file (such as /tmp/fips.o)
+where the linker will write the “FIPS object” that is being hashed.
+
+There is also commented-out code in crypto/internal/fips/check that
+will write /tmp/fipscheck.o during the run-time verification.
+
+When the hashes differ, the first step is to uncomment the
+/tmp/fipscheck.o-writing code and then rebuild with
+-ldflags=-fipso=/tmp/fips.o. Then when the hash check fails,
+compare /tmp/fips.o and /tmp/fipscheck.o to find the differences.
+*/
+
+package ld
+
+import (
+       "bufio"
+       "bytes"
+       "cmd/internal/obj"
+       "cmd/internal/objabi"
+       "cmd/link/internal/loader"
+       "cmd/link/internal/sym"
+       "crypto/hmac"
+       "crypto/sha256"
+       "debug/elf"
+       "debug/macho"
+       "debug/pe"
+       "encoding/binary"
+       "fmt"
+       "hash"
+       "io"
+       "os"
+)
+
+const enableFIPS = false
+
+// fipsSyms are the special FIPS section bracketing symbols.
+var fipsSyms = []struct {
+       name string
+       kind sym.SymKind
+       sym  loader.Sym
+       seg  *sym.Segment
+}{
+       {name: "go:textfipsstart", kind: sym.STEXTFIPSSTART, seg: &Segtext},
+       {name: "go:textfipsend", kind: sym.STEXTFIPSEND},
+       {name: "go:rodatafipsstart", kind: sym.SRODATAFIPSSTART, seg: &Segrodata},
+       {name: "go:rodatafipsend", kind: sym.SRODATAFIPSEND},
+       {name: "go:noptrdatafipsstart", kind: sym.SNOPTRDATAFIPSSTART, seg: &Segdata},
+       {name: "go:noptrdatafipsend", kind: sym.SNOPTRDATAFIPSEND},
+       {name: "go:datafipsstart", kind: sym.SDATAFIPSSTART, seg: &Segdata},
+       {name: "go:datafipsend", kind: sym.SDATAFIPSEND},
+}
+
+// fipsinfo is the loader symbol for go:fipsinfo.
+var fipsinfo loader.Sym
+
+const (
+       fipsMagic    = "\xff Go fipsinfo \xff\x00"
+       fipsMagicLen = 16
+       fipsSumLen   = 32
+)
+
+// loadfips creates the special bracketing symbols and go:fipsinfo.
+func loadfips(ctxt *Link) {
+       if !obj.EnableFIPS() {
+               return
+       }
+       if ctxt.BuildMode == BuildModePlugin { // not sure why this doesn't work
+               return
+       }
+       // Write the fipsinfo symbol, which crypto/internal/fips/check uses.
+       ldr := ctxt.loader
+       // TODO lock down linkname
+       info := ldr.CreateSymForUpdate("go:fipsinfo", 0)
+       info.SetType(sym.SFIPSINFO)
+
+       data := make([]byte, fipsMagicLen+fipsSumLen)
+       copy(data, fipsMagic)
+       info.SetData(data)
+       info.SetSize(int64(len(data)))      // magic + checksum, to be filled in
+       info.AddAddr(ctxt.Arch, info.Sym()) // self-reference
+
+       for i := range fipsSyms {
+               s := &fipsSyms[i]
+               sb := ldr.CreateSymForUpdate(s.name, 0)
+               sb.SetType(s.kind)
+               sb.SetLocal(true)
+               sb.SetSize(1)
+               s.sym = sb.Sym()
+               info.AddAddr(ctxt.Arch, s.sym)
+               if s.kind == sym.STEXTFIPSSTART || s.kind == sym.STEXTFIPSEND {
+                       ctxt.Textp = append(ctxt.Textp, s.sym)
+               }
+       }
+
+       fipsinfo = info.Sym()
+}
+
+// fipsObj calculates the fips object hash and optionally writes
+// the hashed content to a file for debugging.
+type fipsObj struct {
+       r   io.ReaderAt
+       w   io.Writer
+       wf  *os.File
+       h   hash.Hash
+       tmp [8]byte
+}
+
+// newFipsObj creates a fipsObj reading from r and writing to fipso
+// (unless fipso is the empty string, in which case it writes nowhere
+// and only computes the hash).
+func newFipsObj(r io.ReaderAt, fipso string) (*fipsObj, error) {
+       f := &fipsObj{r: r}
+       f.h = hmac.New(sha256.New, make([]byte, 32))
+       f.w = f.h
+       if fipso != "" {
+               wf, err := os.Create(fipso)
+               if err != nil {
+                       return nil, err
+               }
+               f.wf = wf
+               f.w = io.MultiWriter(f.h, wf)
+       }
+
+       if _, err := f.w.Write([]byte("go fips object v1\n")); err != nil {
+               f.Close()
+               return nil, err
+       }
+       return f, nil
+}
+
+// addSection adds the section of r (passed to newFipsObj)
+// starting at byte offset start and ending before byte offset end
+// to the fips object file.
+func (f *fipsObj) addSection(start, end int64) error {
+       n := end - start
+       binary.BigEndian.PutUint64(f.tmp[:], uint64(n))
+       f.w.Write(f.tmp[:])
+       _, err := io.Copy(f.w, io.NewSectionReader(f.r, start, n))
+       return err
+}
+
+// sum returns the hash of the fips object file.
+func (f *fipsObj) sum() []byte {
+       return f.h.Sum(nil)
+}
+
+// Close closes the fipsObj. In particular it closes the output
+// object file specified by fipso in the call to [newFipsObj].
+func (f *fipsObj) Close() error {
+       if f.wf != nil {
+               return f.wf.Close()
+       }
+       return nil
+}
+
+// asmbfips is called from [asmb] to update go:fipsinfo
+// when using internal linking.
+// See [hostlinkfips] for external linking.
+func asmbfips(ctxt *Link, fipso string) {
+       if !obj.EnableFIPS() {
+               return
+       }
+       if ctxt.LinkMode == LinkExternal {
+               return
+       }
+       if ctxt.BuildMode == BuildModePlugin { // not sure why this doesn't work
+               return
+       }
+
+       // Create a new FIPS object with data read from our output file.
+       f, err := newFipsObj(bytes.NewReader(ctxt.Out.Data()), fipso)
+       if err != nil {
+               Errorf("asmbfips: %v", err)
+               return
+       }
+       defer f.Close()
+
+       // Add the FIPS sections to the FIPS object.
+       ldr := ctxt.loader
+       for i := 0; i < len(fipsSyms); i += 2 {
+               start := &fipsSyms[i]
+               end := &fipsSyms[i+1]
+               startAddr := ldr.SymValue(start.sym)
+               endAddr := ldr.SymValue(end.sym)
+               seg := start.seg
+               if seg.Vaddr == 0 && seg == &Segrodata { // some systems use text instead of separate rodata
+                       seg = &Segtext
+               }
+               base := int64(seg.Fileoff - seg.Vaddr)
+               if !(seg.Vaddr <= uint64(startAddr) && startAddr <= endAddr && uint64(endAddr) <= seg.Vaddr+seg.Filelen) {
+                       Errorf("asmbfips: %s not in expected segment (%#x..%#x not in %#x..%#x)", start.name, startAddr, endAddr, seg.Vaddr, seg.Vaddr+seg.Filelen)
+                       return
+               }
+
+               if err := f.addSection(startAddr+base, endAddr+base); err != nil {
+                       Errorf("asmbfips: %v", err)
+                       return
+               }
+       }
+
+       // Overwrite the go:fipsinfo sum field with the calculated sum.
+       addr := uint64(ldr.SymValue(fipsinfo))
+       seg := &Segdata
+       if !(seg.Vaddr <= addr && addr+32 < seg.Vaddr+seg.Filelen) {
+               Errorf("asmbfips: fipsinfo not in expected segment (%#x..%#x not in %#x..%#x)", addr, addr+32, seg.Vaddr, seg.Vaddr+seg.Filelen)
+               return
+       }
+       ctxt.Out.SeekSet(int64(seg.Fileoff + addr - seg.Vaddr + fipsMagicLen))
+       ctxt.Out.Write(f.sum())
+
+       if err := f.Close(); err != nil {
+               Errorf("asmbfips: %v", err)
+               return
+       }
+}
+
+// hostlinkfips is called from [hostlink] to update go:fipsinfo
+// when using external linking.
+// See [asmbfips] for internal linking.
+func hostlinkfips(ctxt *Link, exe, fipso string) error {
+       if !obj.EnableFIPS() {
+               return nil
+       }
+       if ctxt.BuildMode == BuildModePlugin { // not sure why this doesn't work
+               return nil
+       }
+       switch {
+       case ctxt.IsElf():
+               return elffips(ctxt, exe, fipso)
+       case ctxt.HeadType == objabi.Hdarwin:
+               return machofips(ctxt, exe, fipso)
+       case ctxt.HeadType == objabi.Hwindows:
+               return pefips(ctxt, exe, fipso)
+       }
+
+       // If we can't do FIPS, leave the output binary alone.
+       // If people enable FIPS the init-time check will fail,
+       // but the binaries will work otherwise.
+       return fmt.Errorf("fips unsupported on %s", ctxt.HeadType)
+}
+
+// machofips updates go:fipsinfo after external linking
+// on systems using Mach-O (GOOS=darwin, GOOS=ios).
+func machofips(ctxt *Link, exe, fipso string) error {
+       // Open executable both for reading Mach-O and for the fipsObj.
+       mf, err := macho.Open(exe)
+       if err != nil {
+               return err
+       }
+       defer mf.Close()
+
+       wf, err := os.OpenFile(exe, os.O_RDWR, 0)
+       if err != nil {
+               return err
+       }
+       defer wf.Close()
+
+       f, err := newFipsObj(wf, fipso)
+       if err != nil {
+               return err
+       }
+       defer f.Close()
+
+       // Find the go:fipsinfo symbol.
+       sect := mf.Section("__go_fipsinfo")
+       if sect == nil {
+               return fmt.Errorf("cannot find __go_fipsinfo")
+       }
+       data, err := sect.Data()
+       if err != nil {
+               return err
+       }
+
+       uptr := ctxt.Arch.ByteOrder.Uint64
+       if ctxt.Arch.PtrSize == 4 {
+               uptr = func(x []byte) uint64 {
+                       return uint64(ctxt.Arch.ByteOrder.Uint32(x))
+               }
+       }
+
+       // Add the sections listed in go:fipsinfo to the FIPS object.
+       // On Mac, the debug/macho package is not reporting any relocations,
+       // but the addends are all in the data already, all relative to
+       // the same base.
+       // Determine the base used for the self pointer, and then apply
+       // that base to the other uintptrs.
+       // The very high bits of the uint64s seem to be relocation metadata,
+       // so clear them.
+       // For non-pie builds, there are no relocations at all:
+       // the data holds the actual pointers.
+       // This code handles both pie and non-pie binaries.
+       const addendMask = 1<<48 - 1
+       data = data[fipsMagicLen+fipsSumLen:]
+       self := int64(uptr(data)) & addendMask
+       base := int64(sect.Offset) - self
+       data = data[ctxt.Arch.PtrSize:]
+
+       for i := 0; i < 4; i++ {
+               start := int64(uptr(data[0:]))&addendMask + base
+               end := int64(uptr(data[ctxt.Arch.PtrSize:]))&addendMask + base
+               data = data[2*ctxt.Arch.PtrSize:]
+               if err := f.addSection(start, end); err != nil {
+                       return err
+               }
+       }
+
+       // Overwrite the go:fipsinfo sum field with the calculated sum.
+       if _, err := wf.WriteAt(f.sum(), int64(sect.Offset)+fipsMagicLen); err != nil {
+               return err
+       }
+       if err := wf.Close(); err != nil {
+               return err
+       }
+       return f.Close()
+}
+
+// machofips updates go:fipsinfo after external linking
+// on systems using ELF (most Unix systems).
+func elffips(ctxt *Link, exe, fipso string) error {
+       // Open executable both for reading ELF and for the fipsObj.
+       ef, err := elf.Open(exe)
+       if err != nil {
+               return err
+       }
+       defer ef.Close()
+
+       wf, err := os.OpenFile(exe, os.O_RDWR, 0)
+       if err != nil {
+               return err
+       }
+       defer wf.Close()
+
+       f, err := newFipsObj(wf, fipso)
+       if err != nil {
+               return err
+       }
+       defer f.Close()
+
+       // Find the go:fipsinfo symbol.
+       sect := ef.Section(".go.fipsinfo")
+       if sect == nil {
+               return fmt.Errorf("cannot find .go.fipsinfo")
+       }
+
+       data, err := sect.Data()
+       if err != nil {
+               return err
+       }
+
+       uptr := ctxt.Arch.ByteOrder.Uint64
+       if ctxt.Arch.PtrSize == 4 {
+               uptr = func(x []byte) uint64 {
+                       return uint64(ctxt.Arch.ByteOrder.Uint32(x))
+               }
+       }
+
+       // Add the sections listed in go:fipsinfo to the FIPS object.
+       // We expect R_zzz_RELATIVE relocations where the zero-based
+       // values are already stored in the data. That is, the addend
+       // is in the data itself in addition to being in the relocation tables.
+       // So no need to parse the relocation tables unless we find a
+       // toolchain that doesn't initialize the data this way.
+       // For non-pie builds, there are no relocations at all:
+       // the data holds the actual pointers.
+       // This code handles both pie and non-pie binaries.
+       data = data[fipsMagicLen+fipsSumLen:]
+       data = data[ctxt.Arch.PtrSize:]
+
+Addrs:
+       for i := 0; i < 4; i++ {
+               start := uptr(data[0:])
+               end := uptr(data[ctxt.Arch.PtrSize:])
+               data = data[2*ctxt.Arch.PtrSize:]
+               for _, prog := range ef.Progs {
+                       if prog.Type == elf.PT_LOAD && prog.Vaddr <= start && start <= end && end <= prog.Vaddr+prog.Filesz {
+                               if err := f.addSection(int64(start+prog.Off-prog.Vaddr), int64(end+prog.Off-prog.Vaddr)); err != nil {
+                                       return err
+                               }
+                               continue Addrs
+                       }
+               }
+               return fmt.Errorf("invalid pointers found in .go.fipsinfo")
+       }
+
+       // Overwrite the go:fipsinfo sum field with the calculated sum.
+       if _, err := wf.WriteAt(f.sum(), int64(sect.Offset)+fipsMagicLen); err != nil {
+               return err
+       }
+       if err := wf.Close(); err != nil {
+               return err
+       }
+       return f.Close()
+}
+
+// pefips updates go:fipsinfo after external linking
+// on systems using PE (GOOS=windows).
+func pefips(ctxt *Link, exe, fipso string) error {
+       // Open executable both for reading Mach-O and for the fipsObj.
+       pf, err := pe.Open(exe)
+       if err != nil {
+               return err
+       }
+       defer pf.Close()
+
+       wf, err := os.OpenFile(exe, os.O_RDWR, 0)
+       if err != nil {
+               return err
+       }
+       defer wf.Close()
+
+       f, err := newFipsObj(wf, fipso)
+       if err != nil {
+               return err
+       }
+       defer f.Close()
+
+       // Find the go:fipsinfo symbol.
+       // PE does not put it in its own section, so we have to scan for it.
+       // It is near the start of the data segment, right after go:buildinfo,
+       // so we should not have to scan too far.
+       const maxScan = 16 << 20
+       sect := pf.Section(".data")
+       if sect == nil {
+               return fmt.Errorf("cannot find .data")
+       }
+       b := bufio.NewReader(sect.Open())
+       off := int64(0)
+       data := make([]byte, fipsMagicLen+fipsSumLen+9*ctxt.Arch.PtrSize)
+       for ; ; off += 16 {
+               if off >= maxScan {
+                       break
+               }
+               if _, err := io.ReadFull(b, data[:fipsMagicLen]); err != nil {
+                       return fmt.Errorf("scanning PE for FIPS magic: %v", err)
+               }
+               if string(data[:fipsMagicLen]) == fipsMagic {
+                       if _, err := io.ReadFull(b, data[fipsMagicLen:]); err != nil {
+                               return fmt.Errorf("scanning PE for FIPS magic: %v", err)
+                       }
+                       break
+               }
+       }
+
+       uptr := ctxt.Arch.ByteOrder.Uint64
+       if ctxt.Arch.PtrSize == 4 {
+               uptr = func(x []byte) uint64 {
+                       return uint64(ctxt.Arch.ByteOrder.Uint32(x))
+               }
+       }
+
+       // Add the sections listed in go:fipsinfo to the FIPS object.
+       // Determine the base used for the self pointer, and then apply
+       // that base to the other uintptrs.
+       // For pie builds, the addends are in the data.
+       // For non-pie builds, there are no relocations at all:
+       // the data holds the actual pointers.
+       // This code handles both pie and non-pie binaries.
+       data = data[fipsMagicLen+fipsSumLen:]
+       self := int64(uptr(data))
+       data = data[ctxt.Arch.PtrSize:]
+
+       // On 64-bit binaries the pointers have extra bits set
+       // that don't appear in the actual section headers.
+       // For example, one generated test binary looks like:
+       //
+       //      .data VirtualAddress = 0x2af000
+       //      .data (file) Offset = 0x2ac400
+       //      .data (file) Size = 0x1fc00
+       //      go:fipsinfo found at offset 0x2ac5e0 (off=0x1e0)
+       //      go:fipsinfo self pointer = 0x01402af1e0
+       //
+       // From the section headers, the address of the go:fipsinfo symbol
+       // should be 0x2af000 + (0x2ac5e0 - 0x2ac400) = 0x2af1e0,
+       // yet in this case its pointer is 0x1402af1e0, meaning the
+       // data section's VirtualAddress is really 0x1402af000.
+       // This is not (only) a 32-bit truncation problem, since the uint32
+       // truncation of that address would be 0x402af000, not 0x2af000.
+       // Perhaps there is some 64-bit extension that debug/pe is not
+       // reading or is misreading. In any event, we can derive the delta
+       // between computed VirtualAddress and listed VirtualAddress
+       // and apply it to the rest of the pointers.
+       // As a sanity check, the low 12 bits (virtual page offset)
+       // must match between our computed address and the actual one.
+       peself := int64(sect.VirtualAddress) + off
+       if self&0xfff != off&0xfff {
+               return fmt.Errorf("corrupt pointer found in go:fipsinfo")
+       }
+       delta := peself - self
+
+Addrs:
+       for i := 0; i < 4; i++ {
+               start := int64(uptr(data[0:])) + delta
+               end := int64(uptr(data[ctxt.Arch.PtrSize:])) + delta
+               data = data[2*ctxt.Arch.PtrSize:]
+               for _, sect := range pf.Sections {
+                       if int64(sect.VirtualAddress) <= start && start <= end && end <= int64(sect.VirtualAddress)+int64(sect.Size) {
+                               off := int64(sect.Offset) - int64(sect.VirtualAddress)
+                               if err := f.addSection(start+off, end+off); err != nil {
+                                       return err
+                               }
+                               continue Addrs
+                       }
+               }
+               return fmt.Errorf("invalid pointers found in go:fipsinfo")
+       }
+
+       // Overwrite the go:fipsinfo sum field with the calculated sum.
+       if _, err := wf.WriteAt(f.sum(), int64(sect.Offset)+off+fipsMagicLen); err != nil {
+               return err
+       }
+       if err := wf.Close(); err != nil {
+               return err
+       }
+       return f.Close()
+}
index f2cf611b20fd67bea192d02b62dca712c4c21efa..46585479dab0cca442faee2a7c179a89c4834fd5 100644 (file)
@@ -672,6 +672,8 @@ func (ctxt *Link) loadlib() {
                }
        }
 
+       loadfips(ctxt)
+
        // We've loaded all the code now.
        ctxt.Loaded = true
 
@@ -2072,6 +2074,7 @@ func (ctxt *Link) hostlink() {
                                return machoRewriteUuid(ctxt, exef, exem, outexe)
                        })
        }
+       hostlinkfips(ctxt, *flagOutfile, *flagFipso)
        if ctxt.NeedCodeSign() {
                err := machoCodeSign(ctxt, *flagOutfile)
                if err != nil {
index 3aff220a46faa702148a4788e818d8723ea86538..7614b6d194facf7ece35288a81202e55611ae1d4 100644 (file)
@@ -68,6 +68,7 @@ var (
 
        flagOutfile    = flag.String("o", "", "write output to `file`")
        flagPluginPath = flag.String("pluginpath", "", "full path name for plugin")
+       flagFipso      = flag.String("fipso", "", "write fips module to `file`")
 
        flagInstallSuffix = flag.String("installsuffix", "", "set package directory `suffix`")
        flagDumpDep       = flag.Bool("dumpdep", false, "dump symbol dependency graph")
@@ -454,7 +455,6 @@ func Main(arch *sys.Arch, theArch Arch) {
        // will be applied directly there.
        bench.Start("Asmb")
        asmb(ctxt)
-
        exitIfErrors()
 
        // Generate additional symbols for the native symbol table just prior
@@ -464,6 +464,8 @@ func Main(arch *sys.Arch, theArch Arch) {
                thearch.GenSymsLate(ctxt, ctxt.loader)
        }
 
+       asmbfips(ctxt, *flagFipso)
+
        bench.Start("Asmb2")
        asmb2(ctxt)
 
index 7298af5756cb7a26b90ed8e0d3b65017aeceb71c..8156f83a7a3267c5ad7e0238a986322d20a03685 100644 (file)
@@ -431,13 +431,13 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
        // Define these so that they'll get put into the symbol table.
        // data.c:/^address will provide the actual values.
        ctxt.xdefine("runtime.rodata", sym.SRODATA, 0)
-       ctxt.xdefine("runtime.erodata", sym.SRODATA, 0)
+       ctxt.xdefine("runtime.erodata", sym.SRODATAEND, 0)
        ctxt.xdefine("runtime.types", sym.SRODATA, 0)
        ctxt.xdefine("runtime.etypes", sym.SRODATA, 0)
        ctxt.xdefine("runtime.noptrdata", sym.SNOPTRDATA, 0)
-       ctxt.xdefine("runtime.enoptrdata", sym.SNOPTRDATA, 0)
+       ctxt.xdefine("runtime.enoptrdata", sym.SNOPTRDATAEND, 0)
        ctxt.xdefine("runtime.data", sym.SDATA, 0)
-       ctxt.xdefine("runtime.edata", sym.SDATA, 0)
+       ctxt.xdefine("runtime.edata", sym.SDATAEND, 0)
        ctxt.xdefine("runtime.bss", sym.SBSS, 0)
        ctxt.xdefine("runtime.ebss", sym.SBSS, 0)
        ctxt.xdefine("runtime.noptrbss", sym.SNOPTRBSS, 0)
@@ -845,6 +845,9 @@ func setCarrierSym(typ sym.SymKind, s loader.Sym) {
 }
 
 func setCarrierSize(typ sym.SymKind, sz int64) {
+       if typ == sym.Sxxx {
+               panic("setCarrierSize(Sxxx)")
+       }
        if CarrierSymByType[typ].Size != 0 {
                panic(fmt.Sprintf("carrier symbol size for type %v already set", typ))
        }
@@ -852,7 +855,7 @@ func setCarrierSize(typ sym.SymKind, sz int64) {
 }
 
 func isStaticTmp(name string) bool {
-       return strings.Contains(name, "."+obj.StaticNamePref)
+       return strings.Contains(name, "."+obj.StaticNamePrefix)
 }
 
 // Mangle function name with ABI information.
index d99dbbd157d035d1ef2a3262ada64fc69c97eb79..fe11f91526e7b7de066775a336bf00df9de52ce3 100644 (file)
@@ -2339,6 +2339,8 @@ var blockedLinknames = map[string][]string{
        // weak references
        "internal/weak.runtime_registerWeakPointer": {"internal/weak"},
        "internal/weak.runtime_makeStrongFromWeak":  {"internal/weak"},
+       // fips info
+       "go:fipsinfo": {"crypto/internal/fips/check"},
 }
 
 // check if a linkname reference to symbol s from pkg is allowed