cmd/internal/ld, runtime: abort on shared library ABI mismatch

This:

1) Defines the ABI hash of a package (as the SHA1 of the __.PKGDEF)
2) Defines the ABI hash of a shared library (sort the packages by import
   path, concatenate the hashes of the packages and SHA1 that)
3) When building a shared library, compute the above value and define a
   global symbol that points to a go string that has the hash as its value.
4) When linking against a shared library, read the abi hash from the
   library and put both the value seen at link time and a reference
   to the global symbol into the moduledata.
5) During runtime initialization, check that the hash seen at link time
   still matches the hash the global symbol points to.

Change-Id: Iaa54c783790e6dde3057a2feadc35473d49614a5
Reviewed-on: https://go-review.googlesource.com/8773
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Michael Hudson-Doyle <michael.hudson@canonical.com>
diff --git a/src/cmd/internal/ld/data.go b/src/cmd/internal/ld/data.go
index 37d4588..b015754 100644
--- a/src/cmd/internal/ld/data.go
+++ b/src/cmd/internal/ld/data.go
@@ -963,6 +963,22 @@
 	return int64(r)
 }
 
+// addgostring adds str, as a Go string value, to s. symname is the name of the
+// symbol used to define the string data and must be unique per linked object.
+func addgostring(s *LSym, symname, str string) {
+	sym := Linklookup(Ctxt, symname, 0)
+	if sym.Type != obj.Sxxx {
+		Diag("duplicate symname in addgostring: %s", symname)
+	}
+	sym.Reachable = true
+	sym.Local = true
+	sym.Type = obj.SRODATA
+	sym.Size = int64(len(str))
+	sym.P = []byte(str)
+	Addaddr(Ctxt, s, sym)
+	adduint(Ctxt, s, uint64(len(str)))
+}
+
 func addinitarrdata(s *LSym) {
 	p := s.Name + ".ptr"
 	sp := Linklookup(Ctxt, p, 0)
diff --git a/src/cmd/internal/ld/ld.go b/src/cmd/internal/ld/ld.go
index 7242301..1068bdd 100644
--- a/src/cmd/internal/ld/ld.go
+++ b/src/cmd/internal/ld/ld.go
@@ -109,8 +109,8 @@
 		fmt.Fprintf(ctxt.Bso, "%5.2f addlibpath: srcref: %s objref: %s file: %s pkg: %s shlibnamefile: %s\n", obj.Cputime(), srcref, objref, file, pkg, shlibnamefile)
 	}
 
-	ctxt.Library = append(ctxt.Library, Library{})
-	l := &ctxt.Library[len(ctxt.Library)-1]
+	ctxt.Library = append(ctxt.Library, &Library{})
+	l := ctxt.Library[len(ctxt.Library)-1]
 	l.Objref = objref
 	l.Srcref = srcref
 	l.File = file
diff --git a/src/cmd/internal/ld/lib.go b/src/cmd/internal/ld/lib.go
index e4e68ea..d4e6780 100644
--- a/src/cmd/internal/ld/lib.go
+++ b/src/cmd/internal/ld/lib.go
@@ -34,6 +34,7 @@
 	"bufio"
 	"bytes"
 	"cmd/internal/obj"
+	"crypto/sha1"
 	"debug/elf"
 	"fmt"
 	"io"
@@ -474,7 +475,7 @@
 		if Ctxt.Library[i].Shlib != "" {
 			ldshlibsyms(Ctxt.Library[i].Shlib)
 		} else {
-			objfile(Ctxt.Library[i].File, Ctxt.Library[i].Pkg)
+			objfile(Ctxt.Library[i])
 		}
 	}
 
@@ -520,7 +521,7 @@
 				if DynlinkingGo() {
 					Exitf("cannot implicitly include runtime/cgo in a shared library")
 				}
-				objfile(Ctxt.Library[i].File, Ctxt.Library[i].Pkg)
+				objfile(Ctxt.Library[i])
 			}
 		}
 	}
@@ -631,18 +632,18 @@
 	return int64(arsize) + SAR_HDR
 }
 
-func objfile(file string, pkg string) {
-	pkg = pathtoprefix(pkg)
+func objfile(lib *Library) {
+	pkg := pathtoprefix(lib.Pkg)
 
 	if Debug['v'] > 1 {
-		fmt.Fprintf(&Bso, "%5.2f ldobj: %s (%s)\n", obj.Cputime(), file, pkg)
+		fmt.Fprintf(&Bso, "%5.2f ldobj: %s (%s)\n", obj.Cputime(), lib.File, pkg)
 	}
 	Bso.Flush()
 	var err error
 	var f *obj.Biobuf
-	f, err = obj.Bopenr(file)
+	f, err = obj.Bopenr(lib.File)
 	if err != nil {
-		Exitf("cannot open file %s: %v", file, err)
+		Exitf("cannot open file %s: %v", lib.File, err)
 	}
 
 	magbuf := make([]byte, len(ARMAG))
@@ -651,7 +652,7 @@
 		l := obj.Bseek(f, 0, 2)
 
 		obj.Bseek(f, 0, 0)
-		ldobj(f, pkg, l, file, file, FileObj)
+		ldobj(f, pkg, l, lib.File, lib.File, FileObj)
 		obj.Bterm(f)
 
 		return
@@ -664,7 +665,7 @@
 	l := nextar(f, off, &arhdr)
 	var pname string
 	if l <= 0 {
-		Diag("%s: short read on archive file symbol header", file)
+		Diag("%s: short read on archive file symbol header", lib.File)
 		goto out
 	}
 
@@ -672,20 +673,29 @@
 		off += l
 		l = nextar(f, off, &arhdr)
 		if l <= 0 {
-			Diag("%s: short read on archive file symbol header", file)
+			Diag("%s: short read on archive file symbol header", lib.File)
 			goto out
 		}
 	}
 
 	if !strings.HasPrefix(arhdr.name, pkgname) {
-		Diag("%s: cannot find package header", file)
+		Diag("%s: cannot find package header", lib.File)
 		goto out
 	}
 
+	if Buildmode == BuildmodeShared {
+		before := obj.Boffset(f)
+		pkgdefBytes := make([]byte, atolwhex(arhdr.size))
+		obj.Bread(f, pkgdefBytes)
+		hash := sha1.Sum(pkgdefBytes)
+		lib.hash = hash[:]
+		obj.Bseek(f, before, 0)
+	}
+
 	off += l
 
 	if Debug['u'] != 0 {
-		ldpkg(f, pkg, atolwhex(arhdr.size), file, Pkgdef)
+		ldpkg(f, pkg, atolwhex(arhdr.size), lib.File, Pkgdef)
 	}
 
 	/*
@@ -706,14 +716,14 @@
 			break
 		}
 		if l < 0 {
-			Exitf("%s: malformed archive", file)
+			Exitf("%s: malformed archive", lib.File)
 		}
 
 		off += l
 
-		pname = fmt.Sprintf("%s(%s)", file, arhdr.name)
+		pname = fmt.Sprintf("%s(%s)", lib.File, arhdr.name)
 		l = atolwhex(arhdr.size)
-		ldobj(f, pkg, l, pname, file, ArchiveObj)
+		ldobj(f, pkg, l, pname, lib.File, ArchiveObj)
 	}
 
 out:
@@ -974,7 +984,7 @@
 
 	if Linkshared {
 		for _, shlib := range Ctxt.Shlibs {
-			dir, base := filepath.Split(shlib)
+			dir, base := filepath.Split(shlib.Path)
 			argv = append(argv, "-L"+dir)
 			if !rpath.set {
 				argv = append(argv, "-Wl,-rpath="+dir)
@@ -1120,6 +1130,19 @@
 	ldobjfile(Ctxt, f, pkg, eof-obj.Boffset(f), pn)
 }
 
+func readelfsymboldata(f *elf.File, sym *elf.Symbol) []byte {
+	data := make([]byte, sym.Size)
+	sect := f.Sections[sym.Section]
+	if sect.Type != elf.SHT_PROGBITS {
+		Diag("reading %s from non-PROGBITS section", sym.Name)
+	}
+	n, err := sect.ReadAt(data, int64(sym.Value-sect.Offset))
+	if uint64(n) != sym.Size {
+		Diag("reading contents of %s: %v", sym.Name, err)
+	}
+	return data
+}
+
 func ldshlibsyms(shlib string) {
 	found := false
 	libpath := ""
@@ -1134,8 +1157,8 @@
 		Diag("cannot find shared library: %s", shlib)
 		return
 	}
-	for _, processedname := range Ctxt.Shlibs {
-		if processedname == libpath {
+	for _, processedlib := range Ctxt.Shlibs {
+		if processedlib.Path == libpath {
 			return
 		}
 	}
@@ -1167,6 +1190,7 @@
 	// table removed.
 	gcmasks := make(map[uint64][]byte)
 	types := []*LSym{}
+	var hash []byte
 	for _, s := range syms {
 		if elf.ST_TYPE(s.Info) == elf.STT_NOTYPE || elf.ST_TYPE(s.Info) == elf.STT_SECTION {
 			continue
@@ -1178,15 +1202,10 @@
 			continue
 		}
 		if strings.HasPrefix(s.Name, "runtime.gcbits.0x") {
-			data := make([]byte, s.Size)
-			sect := f.Sections[s.Section]
-			if sect.Type == elf.SHT_PROGBITS {
-				n, err := sect.ReadAt(data, int64(s.Value-sect.Offset))
-				if uint64(n) != s.Size {
-					Diag("Error reading contents of %s: %v", s.Name, err)
-				}
-			}
-			gcmasks[s.Value] = data
+			gcmasks[s.Value] = readelfsymboldata(f, &s)
+		}
+		if s.Name == "go.link.abihashbytes" {
+			hash = readelfsymboldata(f, &s)
 		}
 		if elf.ST_BIND(s.Info) != elf.STB_GLOBAL {
 			continue
@@ -1201,14 +1220,8 @@
 		lsym.ElfType = elf.ST_TYPE(s.Info)
 		lsym.File = libpath
 		if strings.HasPrefix(lsym.Name, "type.") {
-			data := make([]byte, s.Size)
-			sect := f.Sections[s.Section]
-			if sect.Type == elf.SHT_PROGBITS {
-				n, err := sect.ReadAt(data, int64(s.Value-sect.Offset))
-				if uint64(n) != s.Size {
-					Diag("Error reading contents of %s: %v", s.Name, err)
-				}
-				lsym.P = data
+			if f.Sections[s.Section].Type == elf.SHT_PROGBITS {
+				lsym.P = readelfsymboldata(f, &s)
 			}
 			if !strings.HasPrefix(lsym.Name, "type..") {
 				types = append(types, lsym)
@@ -1255,7 +1268,7 @@
 		Ctxt.Etextp = last
 	}
 
-	Ctxt.Shlibs = append(Ctxt.Shlibs, libpath)
+	Ctxt.Shlibs = append(Ctxt.Shlibs, Shlib{Path: libpath, Hash: hash})
 }
 
 func mywhatsys() {
diff --git a/src/cmd/internal/ld/link.go b/src/cmd/internal/ld/link.go
index 03da52a..a314ca13 100644
--- a/src/cmd/internal/ld/link.go
+++ b/src/cmd/internal/ld/link.go
@@ -106,6 +106,11 @@
 	Gotype  *LSym
 }
 
+type Shlib struct {
+	Path string
+	Hash []byte
+}
+
 type Link struct {
 	Thechar   int32
 	Thestring string
@@ -122,8 +127,8 @@
 	Nsymbol   int32
 	Tlsg      *LSym
 	Libdir    []string
-	Library   []Library
-	Shlibs    []string
+	Library   []*Library
+	Shlibs    []Shlib
 	Tlsoffset int
 	Diag      func(string, ...interface{})
 	Cursym    *LSym
@@ -149,6 +154,7 @@
 	File   string
 	Pkg    string
 	Shlib  string
+	hash   []byte
 }
 
 type Pcln struct {
diff --git a/src/cmd/internal/ld/symtab.go b/src/cmd/internal/ld/symtab.go
index d6e79dc..ca66541 100644
--- a/src/cmd/internal/ld/symtab.go
+++ b/src/cmd/internal/ld/symtab.go
@@ -32,6 +32,10 @@
 
 import (
 	"cmd/internal/obj"
+	"crypto/sha1"
+	"fmt"
+	"path/filepath"
+	"sort"
 	"strings"
 )
 
@@ -294,6 +298,20 @@
 	Lputl(uint32(v >> 32))
 }
 
+type byPkg []*Library
+
+func (libs byPkg) Len() int {
+	return len(libs)
+}
+
+func (libs byPkg) Less(a, b int) bool {
+	return libs[a].Pkg < libs[b].Pkg
+}
+
+func (libs byPkg) Swap(a, b int) {
+	libs[a], libs[b] = libs[b], libs[a]
+}
+
 func symtab() {
 	dosymtype()
 
@@ -410,6 +428,19 @@
 		}
 	}
 
+	if Buildmode == BuildmodeShared {
+		sort.Sort(byPkg(Ctxt.Library))
+		h := sha1.New()
+		for _, l := range Ctxt.Library {
+			h.Write(l.hash)
+		}
+		abihashgostr := Linklookup(Ctxt, "go.link.abihash."+filepath.Base(outfile), 0)
+		abihashgostr.Reachable = true
+		abihashgostr.Type = obj.SRODATA
+		var hashbytes []byte
+		addgostring(abihashgostr, "go.link.abihashbytes", string(h.Sum(hashbytes)))
+	}
+
 	// Information about the layout of the executable image for the
 	// runtime to use. Any changes here must be matched by changes to
 	// the definition of moduledata in runtime/symtab.go.
@@ -454,6 +485,38 @@
 	Addaddr(Ctxt, moduledata, Linklookup(Ctxt, "runtime.typelink", 0))
 	adduint(Ctxt, moduledata, uint64(ntypelinks))
 	adduint(Ctxt, moduledata, uint64(ntypelinks))
+	if len(Ctxt.Shlibs) > 0 {
+		thismodulename := filepath.Base(outfile)
+		if Buildmode == BuildmodeExe {
+			// When linking an executable, outfile is just "a.out". Make
+			// it something slightly more comprehensible.
+			thismodulename = "the executable"
+		}
+		addgostring(moduledata, "go.link.thismodulename", thismodulename)
+
+		modulehashes := Linklookup(Ctxt, "go.link.abihashes", 0)
+		modulehashes.Reachable = true
+		modulehashes.Local = true
+		modulehashes.Type = obj.SRODATA
+
+		for i, shlib := range Ctxt.Shlibs {
+			// modulehashes[i].modulename
+			modulename := filepath.Base(shlib.Path)
+			addgostring(modulehashes, fmt.Sprintf("go.link.libname.%d", i), modulename)
+
+			// modulehashes[i].linktimehash
+			addgostring(modulehashes, fmt.Sprintf("go.link.linkhash.%d", i), string(shlib.Hash))
+
+			// modulehashes[i].runtimehash
+			abihash := Linklookup(Ctxt, "go.link.abihash."+modulename, 0)
+			abihash.Reachable = true
+			Addaddr(Ctxt, modulehashes, abihash)
+		}
+
+		Addaddr(Ctxt, moduledata, modulehashes)
+		adduint(Ctxt, moduledata, uint64(len(Ctxt.Shlibs)))
+		adduint(Ctxt, moduledata, uint64(len(Ctxt.Shlibs)))
+	}
 	// The rest of moduledata is zero initialized.
 	// When linking an object that does not contain the runtime we are
 	// creating the moduledata from scratch and it does not have a