aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/link/internal
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/link/internal')
-rw-r--r--src/cmd/link/internal/ld/data.go36
-rw-r--r--src/cmd/link/internal/ld/elf.go2
-rw-r--r--src/cmd/link/internal/ld/fips.go599
-rw-r--r--src/cmd/link/internal/ld/lib.go3
-rw-r--r--src/cmd/link/internal/ld/main.go4
-rw-r--r--src/cmd/link/internal/ld/symtab.go11
-rw-r--r--src/cmd/link/internal/loader/loader.go2
7 files changed, 648 insertions, 9 deletions
diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go
index 31a1d4f160..a23e87d326 100644
--- a/src/cmd/link/internal/ld/data.go
+++ b/src/cmd/link/internal/ld/data.go
@@ -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))
diff --git a/src/cmd/link/internal/ld/elf.go b/src/cmd/link/internal/ld/elf.go
index 3a418d3b61..e6a525198f 100644
--- a/src/cmd/link/internal/ld/elf.go
+++ b/src/cmd/link/internal/ld/elf.go
@@ -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
index 0000000000..8223da49d7
--- /dev/null
+++ b/src/cmd/link/internal/ld/fips.go
@@ -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()
+}
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
index f2cf611b20..46585479da 100644
--- a/src/cmd/link/internal/ld/lib.go
+++ b/src/cmd/link/internal/ld/lib.go
@@ -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 {
diff --git a/src/cmd/link/internal/ld/main.go b/src/cmd/link/internal/ld/main.go
index 3aff220a46..7614b6d194 100644
--- a/src/cmd/link/internal/ld/main.go
+++ b/src/cmd/link/internal/ld/main.go
@@ -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)
diff --git a/src/cmd/link/internal/ld/symtab.go b/src/cmd/link/internal/ld/symtab.go
index 7298af5756..8156f83a7a 100644
--- a/src/cmd/link/internal/ld/symtab.go
+++ b/src/cmd/link/internal/ld/symtab.go
@@ -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.
diff --git a/src/cmd/link/internal/loader/loader.go b/src/cmd/link/internal/loader/loader.go
index d99dbbd157..fe11f91526 100644
--- a/src/cmd/link/internal/loader/loader.go
+++ b/src/cmd/link/internal/loader/loader.go
@@ -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