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"`
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.
}
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.
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)
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)
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:
//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)
}
case ir.OSLICELIT:
+ if disableGlobalAddrs {
+ return false
+ }
r := r.(*ir.CompLitExpr)
s.initplan(r)
// Init slice.
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
}
break
case ir.OCLOSURE:
+ if disableGlobalAddrs {
+ return false
+ }
r := r.(*ir.ClosureExpr)
if !r.Func.IsClosure() {
if base.Debug.Closure > 0 {
// 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)
return true
case ir.OINLCALL:
+ if disableGlobalAddrs {
+ return false
+ }
r := r.(*ir.InlinedCallExpr)
return s.staticAssignInlinedCall(l, loff, r, typ)
}
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)
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 {
//
// 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())
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)
}
// 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)
}
--- /dev/null
+// 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)
+}
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
} else if flag&TLSBSS != 0 {
s.Type = objabi.STLSBSS
}
+ s.setFIPSType(ctxt)
}
// EmitEntryLiveness generates PCDATA Progs after p to switch to the
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
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'
// 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
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
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)
// Writable data sections that do not need any specialized handling.
writable := []sym.SymKind{
sym.SBUILDINFO,
+ sym.SFIPSINFO,
sym.SELFSECT,
sym.SMACHO,
sym.SMACHOGOT,
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 */
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
}
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])
})
}
+ // 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
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))
shstrtabAddstring(".noptrbss")
shstrtabAddstring(".go.fuzzcntrs")
shstrtabAddstring(".go.buildinfo")
+ shstrtabAddstring(".go.fipsinfo")
if ctxt.IsMIPS() {
shstrtabAddstring(".MIPS.abiflags")
shstrtabAddstring(".gnu.attributes")
shstrtabAddstring(elfRelType + ".data.rel.ro")
}
shstrtabAddstring(elfRelType + ".go.buildinfo")
+ shstrtabAddstring(elfRelType + ".go.fipsinfo")
if ctxt.IsMIPS() {
shstrtabAddstring(elfRelType + ".MIPS.abiflags")
shstrtabAddstring(elfRelType + ".gnu.attributes")
--- /dev/null
+// 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()
+}
}
}
+ loadfips(ctxt)
+
// We've loaded all the code now.
ctxt.Loaded = true
return machoRewriteUuid(ctxt, exef, exem, outexe)
})
}
+ hostlinkfips(ctxt, *flagOutfile, *flagFipso)
if ctxt.NeedCodeSign() {
err := machoCodeSign(ctxt, *flagOutfile)
if err != nil {
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")
// will be applied directly there.
bench.Start("Asmb")
asmb(ctxt)
-
exitIfErrors()
// Generate additional symbols for the native symbol table just prior
thearch.GenSymsLate(ctxt, ctxt.loader)
}
+ asmbfips(ctxt, *flagFipso)
+
bench.Start("Asmb2")
asmb2(ctxt)
// 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)
}
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))
}
}
func isStaticTmp(name string) bool {
- return strings.Contains(name, "."+obj.StaticNamePref)
+ return strings.Contains(name, "."+obj.StaticNamePrefix)
}
// Mangle function name with ABI information.
// 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